Merge pull request #25 from djwesty/djwesty/7
Implemented Automatic Detection of chip counts and colors # 7
This commit is contained in:
commit
6f10efaac0
3
.env.example
Normal file
3
.env.example
Normal file
@ -0,0 +1,3 @@
|
||||
API_KEY=Put Open AI key here
|
||||
GPT_MODEL=gpt-4o-mini
|
||||
#GPT_MODEL=gpt-4-turbo # More expensive model, use sparingly
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -36,3 +36,5 @@ yarn-error.*
|
||||
*.tsbuildinfo
|
||||
|
||||
app-example
|
||||
android
|
||||
.env
|
17
README.md
17
README.md
@ -14,6 +14,23 @@ This applications uses the React Native + Expo framework and by extension is pri
|
||||
|
||||
This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app).
|
||||
|
||||
### Setting Up Environment Variables
|
||||
|
||||
To set up your environment variables:
|
||||
|
||||
1. Copy the example environment variable file to create your own .env file:
|
||||
|
||||
cp .env.example .env
|
||||
|
||||
2. Open the .env file and add your OpenAI API key:
|
||||
|
||||
API_KEY=your_openai_api_key_here
|
||||
MODEL_NAME=your_model_name
|
||||
|
||||
3. Save the .env file.
|
||||
|
||||
This setup allows you to run the application with your own API credentials, and you can switch models if needed.
|
||||
|
||||
### VSCode plugins
|
||||
|
||||
- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
|
||||
|
3
app.json
3
app.json
@ -15,7 +15,8 @@
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/images/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
}
|
||||
},
|
||||
"package": "com.anonymous.pokerchipshelper"
|
||||
},
|
||||
"web": {
|
||||
"bundler": "metro",
|
||||
|
@ -3,12 +3,15 @@ import { ScrollView, Text, Alert, Button } from "react-native";
|
||||
import PlayerSelector from "@/components/PlayerSelector";
|
||||
import BuyInSelector from "@/components/BuyInSelector";
|
||||
import ChipsSelector from "@/components/ChipsSelector";
|
||||
import ChipDistributionSummary from "@/components/ChipDistributionSummary";
|
||||
import ChipDistributionSummary from "@/components/ChipDistributionSummary";
|
||||
import ChipDetection from "@/components/ChipDetection";
|
||||
|
||||
const IndexScreen = () => {
|
||||
const [playerCount, setPlayerCount] = useState(2);
|
||||
const [buyInAmount, setBuyInAmount] = useState<number | null>(null);
|
||||
const [numberOfChips, setNumberOfChips] = useState<number>(5);
|
||||
const [totalChipsCount, setTotalChipsCount] = useState<number[]>([]);
|
||||
|
||||
const handleSave = () => {
|
||||
if (buyInAmount === null) {
|
||||
Alert.alert("Error", "Please select a valid buy-in amount");
|
||||
@ -19,6 +22,16 @@ const IndexScreen = () => {
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Update chip count based on detection or manual edit
|
||||
const updateChipCount = (chipData: { [color: string]: number }) => {
|
||||
// Convert the chip data from the API response or manual edit to a count array
|
||||
const chipCountArray = Object.entries(chipData).map(
|
||||
([color, count]) => count
|
||||
);
|
||||
setTotalChipsCount(chipCountArray); // Update the parent component's state
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollView contentContainerStyle={{ padding: 20, flexGrow: 1 }}>
|
||||
<Text style={{ fontSize: 24, marginBottom: 30, marginTop: 50 }}>
|
||||
@ -29,6 +42,7 @@ const IndexScreen = () => {
|
||||
setPlayerCount={setPlayerCount}
|
||||
/>
|
||||
<BuyInSelector setBuyInAmount={setBuyInAmount} />
|
||||
<ChipDetection updateChipCount={updateChipCount} />
|
||||
<ChipsSelector
|
||||
totalChipsCount={totalChipsCount}
|
||||
setTotalChipsCount={setTotalChipsCount}
|
||||
@ -48,4 +62,5 @@ const IndexScreen = () => {
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
export default IndexScreen;
|
||||
|
15
babel.config.js
Normal file
15
babel.config.js
Normal file
@ -0,0 +1,15 @@
|
||||
module.exports = {
|
||||
presets: ["babel-preset-expo", "@babel/preset-typescript"],
|
||||
plugins: [
|
||||
[
|
||||
"module:react-native-dotenv",
|
||||
{
|
||||
moduleName: "@env",
|
||||
path: ".env",
|
||||
safe: true,
|
||||
allowUndefined: false,
|
||||
},
|
||||
],
|
||||
"react-native-reanimated/plugin",
|
||||
],
|
||||
};
|
136
components/ChipDetection.tsx
Normal file
136
components/ChipDetection.tsx
Normal file
@ -0,0 +1,136 @@
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
View,
|
||||
Button,
|
||||
Image,
|
||||
ActivityIndicator,
|
||||
Text,
|
||||
ScrollView,
|
||||
} from "react-native";
|
||||
import * as ImagePicker from "expo-image-picker";
|
||||
import { API_KEY, MODEL_NAME } from "@env";
|
||||
|
||||
const ChipDetection = ({ updateChipCount }) => {
|
||||
const [imageUri, setImageUri] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [lastDetectedChips, setLastDetectedChips] = useState({});
|
||||
|
||||
const requestCameraPermissions = async () => {
|
||||
const cameraPermission = await ImagePicker.requestCameraPermissionsAsync();
|
||||
return cameraPermission.granted;
|
||||
};
|
||||
|
||||
const pickImage = async () => {
|
||||
const result = await ImagePicker.launchImageLibraryAsync({
|
||||
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
||||
base64: true,
|
||||
quality: 1,
|
||||
});
|
||||
|
||||
if (!result.canceled) {
|
||||
setImageUri(result.assets[0].uri);
|
||||
await processImage(result.assets[0].base64);
|
||||
}
|
||||
};
|
||||
|
||||
const takePhoto = async () => {
|
||||
const hasPermission = await requestCameraPermissions();
|
||||
if (!hasPermission) {
|
||||
setError("Camera permission is required to take a photo.");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await ImagePicker.launchCameraAsync({
|
||||
base64: true,
|
||||
quality: 1,
|
||||
});
|
||||
|
||||
if (!result.canceled) {
|
||||
setImageUri(result.assets[0].uri);
|
||||
await processImage(result.assets[0].base64);
|
||||
}
|
||||
};
|
||||
|
||||
const processImage = async (base64Image) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
"https://api.openai.com/v1/chat/completions",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${API_KEY}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: MODEL_NAME,
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content:
|
||||
"Identify and count poker chips by color. Return only the count for each color in JSON format.",
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "How many poker chips are there for each color? Return structured JSON.",
|
||||
},
|
||||
{
|
||||
type: "image_url",
|
||||
image_url: { url: `data:image/png;base64,${base64Image}` },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
max_tokens: 1000,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok || !result.choices || !result.choices[0].message) {
|
||||
throw new Error("Invalid response from API.");
|
||||
}
|
||||
|
||||
const rawContent = result.choices[0].message.content.trim();
|
||||
const cleanJSON = rawContent.replace(/```json|```/g, "").trim();
|
||||
|
||||
const parsedData = JSON.parse(cleanJSON);
|
||||
|
||||
// Filter out colors with a count of 0
|
||||
const filteredData = Object.fromEntries(
|
||||
Object.entries(parsedData).filter(([_, count]) => count > 0)
|
||||
);
|
||||
|
||||
setLastDetectedChips(filteredData); // Store detected chip counts
|
||||
updateChipCount(filteredData);
|
||||
} catch (error) {
|
||||
setError("Failed to analyze the image.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollView contentContainerStyle={{ padding: 20, alignItems: "center" }}>
|
||||
<Button title="Pick an Image" onPress={pickImage} />
|
||||
<Button title="Take a Photo" onPress={takePhoto} />
|
||||
{imageUri && (
|
||||
<Image
|
||||
source={{ uri: imageUri }}
|
||||
style={{ width: 300, height: 300, marginTop: 10 }}
|
||||
/>
|
||||
)}
|
||||
{loading && <ActivityIndicator size="large" color="blue" />}
|
||||
{error && <Text style={{ color: "red", marginTop: 10 }}>{error}</Text>}
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChipDetection;
|
@ -9,7 +9,7 @@ interface ChipDistributionSummaryProps {
|
||||
colors?: ColorValue[];
|
||||
}
|
||||
|
||||
const MAX_CHIPS = 500;
|
||||
const MAX_CHIPS = 500;
|
||||
|
||||
const ChipDistributionSummary = ({
|
||||
playerCount,
|
||||
@ -22,16 +22,18 @@ const ChipDistributionSummary = ({
|
||||
useEffect(() => {
|
||||
if (buyInAmount !== null && playerCount > 0) {
|
||||
let totalChips = totalChipsCount.reduce((sum, count) => sum + count, 0);
|
||||
|
||||
|
||||
if (totalChips > MAX_CHIPS) {
|
||||
const scaleFactor = MAX_CHIPS / totalChips;
|
||||
totalChipsCount = totalChipsCount.map((count) => Math.floor(count * scaleFactor));
|
||||
totalChipsCount = totalChipsCount.map((count) =>
|
||||
Math.floor(count * scaleFactor)
|
||||
);
|
||||
totalChips = MAX_CHIPS;
|
||||
}
|
||||
const distribution = totalChipsCount.map((chipCount) =>
|
||||
Math.floor(chipCount / playerCount)
|
||||
);
|
||||
|
||||
|
||||
setChipDistribution(distribution);
|
||||
} else {
|
||||
setChipDistribution([]);
|
||||
@ -49,12 +51,14 @@ const ChipDistributionSummary = ({
|
||||
<Text style={styles.title}>Chip Distribution Summary:</Text>
|
||||
{hasValidDistribution ? (
|
||||
chipDistribution.map((count, index) => (
|
||||
<Text key={index} style={[styles.chipText, { color: colors[index] }]}>
|
||||
{`${colors[index]?.toString().toUpperCase()} Chips: ${count} per player`}
|
||||
<Text key={index} style={[styles.chipText, { color: colors[index] }]}>
|
||||
{`${colors[index]?.toString().toUpperCase()} Chips: ${count} per player`}
|
||||
</Text>
|
||||
))
|
||||
) : (
|
||||
<Text style={styles.noDataText}>No valid distribution calculated yet.</Text>
|
||||
<Text style={styles.noDataText}>
|
||||
No valid distribution calculated yet.
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
@ -82,4 +86,4 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
export default ChipDistributionSummary;
|
||||
export default ChipDistributionSummary;
|
||||
|
150
components/__tests__/ChipDetection.test.tsx
Normal file
150
components/__tests__/ChipDetection.test.tsx
Normal file
@ -0,0 +1,150 @@
|
||||
import React from "react";
|
||||
import { render, fireEvent, waitFor } from "@testing-library/react-native";
|
||||
import ChipDetection from "@/components/ChipDetection";
|
||||
import * as ImagePicker from "expo-image-picker";
|
||||
|
||||
const mockUpdateChipCount = jest.fn();
|
||||
|
||||
jest.mock("expo-image-picker", () => ({
|
||||
requestCameraPermissionsAsync: jest.fn(),
|
||||
launchImageLibraryAsync: jest.fn(),
|
||||
launchCameraAsync: jest.fn(),
|
||||
MediaTypeOptions: {
|
||||
Images: "image",
|
||||
},
|
||||
}));
|
||||
|
||||
describe("ChipDetection", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
global.fetch = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: JSON.stringify({ red: 5, green: 3, blue: 0 }),
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("renders correctly", () => {
|
||||
const { getByText } = render(
|
||||
<ChipDetection updateChipCount={mockUpdateChipCount} />
|
||||
);
|
||||
expect(getByText("Pick an Image")).toBeTruthy();
|
||||
expect(getByText("Take a Photo")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("picks an image from the library", async () => {
|
||||
ImagePicker.launchImageLibraryAsync.mockResolvedValueOnce({
|
||||
canceled: false,
|
||||
assets: [{ uri: "test-uri", base64: "test-base64" }],
|
||||
});
|
||||
|
||||
const { getByText } = render(
|
||||
<ChipDetection updateChipCount={mockUpdateChipCount} />
|
||||
);
|
||||
fireEvent.press(getByText("Pick an Image"));
|
||||
|
||||
await waitFor(() => expect(mockUpdateChipCount).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it("takes a photo with the camera", async () => {
|
||||
ImagePicker.requestCameraPermissionsAsync.mockResolvedValueOnce({
|
||||
granted: true,
|
||||
});
|
||||
ImagePicker.launchCameraAsync.mockResolvedValueOnce({
|
||||
canceled: false,
|
||||
assets: [{ uri: "test-camera-uri", base64: "test-camera-base64" }],
|
||||
});
|
||||
|
||||
const { getByText } = render(
|
||||
<ChipDetection updateChipCount={mockUpdateChipCount} />
|
||||
);
|
||||
fireEvent.press(getByText("Take a Photo"));
|
||||
|
||||
await waitFor(() => expect(mockUpdateChipCount).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it("handles camera permission denied", async () => {
|
||||
ImagePicker.requestCameraPermissionsAsync.mockResolvedValueOnce({
|
||||
granted: false,
|
||||
});
|
||||
|
||||
const { getByText } = render(
|
||||
<ChipDetection updateChipCount={mockUpdateChipCount} />
|
||||
);
|
||||
fireEvent.press(getByText("Take a Photo"));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
getByText("Camera permission is required to take a photo.")
|
||||
).toBeTruthy()
|
||||
);
|
||||
});
|
||||
|
||||
it("displays error message on image processing failure", async () => {
|
||||
ImagePicker.launchImageLibraryAsync.mockResolvedValueOnce({
|
||||
canceled: false,
|
||||
assets: [{ uri: "test-uri", base64: "test-base64" }],
|
||||
});
|
||||
|
||||
global.fetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: false,
|
||||
json: () => Promise.resolve({ choices: [] }),
|
||||
})
|
||||
);
|
||||
|
||||
const { getByText } = render(
|
||||
<ChipDetection updateChipCount={mockUpdateChipCount} />
|
||||
);
|
||||
fireEvent.press(getByText("Pick an Image"));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(getByText("Failed to analyze the image.")).toBeTruthy()
|
||||
);
|
||||
});
|
||||
|
||||
it("handles valid API response correctly", async () => {
|
||||
ImagePicker.launchImageLibraryAsync.mockResolvedValueOnce({
|
||||
canceled: false,
|
||||
assets: [{ uri: "test-uri", base64: "test-base64" }],
|
||||
});
|
||||
|
||||
global.fetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: JSON.stringify({ red: 5, green: 3 }),
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
const { getByText } = render(
|
||||
<ChipDetection updateChipCount={mockUpdateChipCount} />
|
||||
);
|
||||
fireEvent.press(getByText("Pick an Image"));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockUpdateChipCount).toHaveBeenCalledWith({
|
||||
red: 5,
|
||||
green: 3,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
296
package-lock.json
generated
296
package-lock.json
generated
@ -8,14 +8,17 @@
|
||||
"name": "poker-chips-helper",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.26.0",
|
||||
"@expo/vector-icons": "^14.0.2",
|
||||
"@react-navigation/bottom-tabs": "^7.2.0",
|
||||
"@react-navigation/native": "^7.0.14",
|
||||
"expo": "~52.0.31",
|
||||
"expo-blur": "~14.0.3",
|
||||
"expo-constants": "~17.0.5",
|
||||
"expo-file-system": "~18.0.11",
|
||||
"expo-font": "~13.0.3",
|
||||
"expo-haptics": "~14.0.1",
|
||||
"expo-image-picker": "~16.0.6",
|
||||
"expo-linking": "~7.0.5",
|
||||
"expo-router": "~4.0.17",
|
||||
"expo-splash-screen": "~0.29.21",
|
||||
@ -23,9 +26,11 @@
|
||||
"expo-symbols": "~0.2.2",
|
||||
"expo-system-ui": "~4.0.8",
|
||||
"expo-web-browser": "~14.0.2",
|
||||
"metro-react-native-babel-preset": "^0.77.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-native": "0.76.7",
|
||||
"react-native": "^0.76.7",
|
||||
"react-native-dotenv": "^3.4.11",
|
||||
"react-native-gesture-handler": "~2.20.2",
|
||||
"react-native-reanimated": "~3.16.1",
|
||||
"react-native-safe-area-context": "4.12.0",
|
||||
@ -34,7 +39,7 @@
|
||||
"react-native-webview": "13.12.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/core": "^7.26.9",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-native": "^5.4.3",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
@ -47,11 +52,12 @@
|
||||
"eslint-plugin-prettier": "^5.2.3",
|
||||
"eslint-plugin-react": "^7.37.4",
|
||||
"eslint-plugin-react-native": "^5.0.0",
|
||||
"jest": "^29.2.1",
|
||||
"jest": "^29.7.0",
|
||||
"jest-expo": "~52.0.3",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"prettier": "^3.4.2",
|
||||
"react-test-renderer": "18.3.1",
|
||||
"typescript": "^5.3.3"
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@0no-co/graphql.web": {
|
||||
@ -105,21 +111,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/core": {
|
||||
"version": "7.26.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.7.tgz",
|
||||
"integrity": "sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==",
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz",
|
||||
"integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.26.2",
|
||||
"@babel/generator": "^7.26.5",
|
||||
"@babel/generator": "^7.26.9",
|
||||
"@babel/helper-compilation-targets": "^7.26.5",
|
||||
"@babel/helper-module-transforms": "^7.26.0",
|
||||
"@babel/helpers": "^7.26.7",
|
||||
"@babel/parser": "^7.26.7",
|
||||
"@babel/template": "^7.25.9",
|
||||
"@babel/traverse": "^7.26.7",
|
||||
"@babel/types": "^7.26.7",
|
||||
"@babel/helpers": "^7.26.9",
|
||||
"@babel/parser": "^7.26.9",
|
||||
"@babel/template": "^7.26.9",
|
||||
"@babel/traverse": "^7.26.9",
|
||||
"@babel/types": "^7.26.9",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.2",
|
||||
@ -135,13 +141,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.26.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz",
|
||||
"integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==",
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz",
|
||||
"integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.26.5",
|
||||
"@babel/types": "^7.26.5",
|
||||
"@babel/parser": "^7.26.9",
|
||||
"@babel/types": "^7.26.9",
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"jsesc": "^3.0.2"
|
||||
@ -232,6 +238,18 @@
|
||||
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-environment-visitor": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz",
|
||||
"integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.24.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-member-expression-to-functions": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz",
|
||||
@ -385,13 +403,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.26.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz",
|
||||
"integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==",
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz",
|
||||
"integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.25.9",
|
||||
"@babel/types": "^7.26.7"
|
||||
"@babel/template": "^7.26.9",
|
||||
"@babel/types": "^7.26.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@ -484,12 +502,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.26.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz",
|
||||
"integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==",
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz",
|
||||
"integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.26.7"
|
||||
"@babel/types": "^7.26.9"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
@ -582,6 +600,25 @@
|
||||
"@babel/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-proposal-async-generator-functions": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz",
|
||||
"integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==",
|
||||
"deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-environment-visitor": "^7.18.9",
|
||||
"@babel/helper-plugin-utils": "^7.20.2",
|
||||
"@babel/helper-remap-async-to-generator": "^7.18.9",
|
||||
"@babel/plugin-syntax-async-generators": "^7.8.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-proposal-class-properties": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz",
|
||||
@ -648,6 +685,60 @@
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-proposal-numeric-separator": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz",
|
||||
"integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==",
|
||||
"deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.18.6",
|
||||
"@babel/plugin-syntax-numeric-separator": "^7.10.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-proposal-object-rest-spread": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz",
|
||||
"integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==",
|
||||
"deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.20.5",
|
||||
"@babel/helper-compilation-targets": "^7.20.7",
|
||||
"@babel/helper-plugin-utils": "^7.20.2",
|
||||
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
|
||||
"@babel/plugin-transform-parameters": "^7.20.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-proposal-optional-catch-binding": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz",
|
||||
"integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==",
|
||||
"deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead.",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.18.6",
|
||||
"@babel/plugin-syntax-optional-catch-binding": "^7.8.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-proposal-optional-chaining": {
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz",
|
||||
@ -2158,30 +2249,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
|
||||
"integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
|
||||
"integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.25.9",
|
||||
"@babel/parser": "^7.25.9",
|
||||
"@babel/types": "^7.25.9"
|
||||
"@babel/code-frame": "^7.26.2",
|
||||
"@babel/parser": "^7.26.9",
|
||||
"@babel/types": "^7.26.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.26.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz",
|
||||
"integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==",
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz",
|
||||
"integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.26.2",
|
||||
"@babel/generator": "^7.26.5",
|
||||
"@babel/parser": "^7.26.7",
|
||||
"@babel/template": "^7.25.9",
|
||||
"@babel/types": "^7.26.7",
|
||||
"@babel/generator": "^7.26.9",
|
||||
"@babel/parser": "^7.26.9",
|
||||
"@babel/template": "^7.26.9",
|
||||
"@babel/types": "^7.26.9",
|
||||
"debug": "^4.3.1",
|
||||
"globals": "^11.1.0"
|
||||
},
|
||||
@ -2209,9 +2300,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.26.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz",
|
||||
"integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==",
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz",
|
||||
"integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.25.9",
|
||||
@ -8075,9 +8166,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/expo-file-system": {
|
||||
"version": "18.0.10",
|
||||
"resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-18.0.10.tgz",
|
||||
"integrity": "sha512-+GnxkI+J9tOzUQMx+uIOLBEBsO2meyoYHxd87m9oT9M//BpepYqI1AvYBH8YM4dgr9HaeaeLr7z5XFVqfL8tWg==",
|
||||
"version": "18.0.11",
|
||||
"resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-18.0.11.tgz",
|
||||
"integrity": "sha512-yDwYfEzWgPXsBZHJW2RJ8Q66ceiFN9Wa5D20pp3fjXVkzPBDwxnYwiPWk4pVmCa5g4X5KYMoMne1pUrsL4OEpg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"web-streams-polyfill": "^3.3.2"
|
||||
@ -8109,6 +8200,27 @@
|
||||
"expo": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/expo-image-loader": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-5.0.0.tgz",
|
||||
"integrity": "sha512-Eg+5FHtyzv3Jjw9dHwu2pWy4xjf8fu3V0Asyy42kO+t/FbvW/vjUixpTjPtgKQLQh+2/9Nk4JjFDV6FwCnF2ZA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"expo": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/expo-image-picker": {
|
||||
"version": "16.0.6",
|
||||
"resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-16.0.6.tgz",
|
||||
"integrity": "sha512-HN4xZirFjsFDIsWFb12AZh19fRzuvZjj2ll17cGr19VNRP06S/VPQU3Tdccn5vwUzQhOBlLu704CnNm278boiQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"expo-image-loader": "~5.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"expo": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/expo-keep-awake": {
|
||||
"version": "14.0.2",
|
||||
"resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-14.0.2.tgz",
|
||||
@ -10587,6 +10699,17 @@
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/jest-fetch-mock": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz",
|
||||
"integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cross-fetch": "^3.0.4",
|
||||
"promise-polyfill": "^8.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-get-type": {
|
||||
"version": "29.6.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
|
||||
@ -12096,6 +12219,68 @@
|
||||
"node": ">=18.18"
|
||||
}
|
||||
},
|
||||
"node_modules/metro-react-native-babel-preset": {
|
||||
"version": "0.77.0",
|
||||
"resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.77.0.tgz",
|
||||
"integrity": "sha512-HPPD+bTxADtoE4y/4t1txgTQ1LVR6imOBy7RMHUsqMVTbekoi8Ph5YI9vKX2VMPtVWeFt0w9YnCSLPa76GcXsA==",
|
||||
"license": "MIT",
|
||||
"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"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/metro-react-native-babel-preset/node_modules/react-refresh": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz",
|
||||
"integrity": "sha512-Hwln1VNuGl/6bVwnd0Xdn1e84gT/8T9aYNL+HAKDArLCS7LWjwr7StE30IEYbIkx0Vi3vs+coQxe+SQDbGbbpA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/metro-resolver": {
|
||||
"version": "0.81.1",
|
||||
"resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.81.1.tgz",
|
||||
@ -13573,6 +13758,13 @@
|
||||
"asap": "~2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/promise-polyfill": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz",
|
||||
"integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/prompts": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
|
||||
@ -13916,6 +14108,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-dotenv": {
|
||||
"version": "3.4.11",
|
||||
"resolved": "https://registry.npmjs.org/react-native-dotenv/-/react-native-dotenv-3.4.11.tgz",
|
||||
"integrity": "sha512-6vnIE+WHABSeHCaYP6l3O1BOEhWxKH6nHAdV7n/wKn/sciZ64zPPp2NUdEUf1m7g4uuzlLbjgr+6uDt89q2DOg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dotenv": "^16.4.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": "^7.20.6"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-gesture-handler": {
|
||||
"version": "2.20.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.20.2.tgz",
|
||||
|
14
package.json
14
package.json
@ -15,14 +15,17 @@
|
||||
"preset": "jest-expo"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.26.0",
|
||||
"@expo/vector-icons": "^14.0.2",
|
||||
"@react-navigation/bottom-tabs": "^7.2.0",
|
||||
"@react-navigation/native": "^7.0.14",
|
||||
"expo": "~52.0.31",
|
||||
"expo-blur": "~14.0.3",
|
||||
"expo-constants": "~17.0.5",
|
||||
"expo-file-system": "~18.0.11",
|
||||
"expo-font": "~13.0.3",
|
||||
"expo-haptics": "~14.0.1",
|
||||
"expo-image-picker": "~16.0.6",
|
||||
"expo-linking": "~7.0.5",
|
||||
"expo-router": "~4.0.17",
|
||||
"expo-splash-screen": "~0.29.21",
|
||||
@ -30,9 +33,11 @@
|
||||
"expo-symbols": "~0.2.2",
|
||||
"expo-system-ui": "~4.0.8",
|
||||
"expo-web-browser": "~14.0.2",
|
||||
"metro-react-native-babel-preset": "^0.77.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-native": "0.76.7",
|
||||
"react-native": "^0.76.7",
|
||||
"react-native-dotenv": "^3.4.11",
|
||||
"react-native-gesture-handler": "~2.20.2",
|
||||
"react-native-reanimated": "~3.16.1",
|
||||
"react-native-safe-area-context": "4.12.0",
|
||||
@ -41,7 +46,7 @@
|
||||
"react-native-webview": "13.12.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/core": "^7.26.9",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-native": "^5.4.3",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
@ -54,11 +59,12 @@
|
||||
"eslint-plugin-prettier": "^5.2.3",
|
||||
"eslint-plugin-react": "^7.37.4",
|
||||
"eslint-plugin-react-native": "^5.0.0",
|
||||
"jest": "^29.2.1",
|
||||
"jest": "^29.7.0",
|
||||
"jest-expo": "~52.0.3",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"prettier": "^3.4.2",
|
||||
"react-test-renderer": "18.3.1",
|
||||
"typescript": "^5.3.3"
|
||||
"typescript": "^5.7.3"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
@ -1,18 +1,14 @@
|
||||
{
|
||||
"extends": "expo/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"jsx": "react",
|
||||
"jsx": "react-native", // Update this to "react-native" for React Native compatibility
|
||||
"strict": true,
|
||||
"esModuleInterop": true, // Ensures compatibility with modules
|
||||
"skipLibCheck": true, // Skips type checking of declaration files for faster builds
|
||||
"moduleResolution": "node", // Ensures modules are resolved correctly
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".expo/types/**/*.ts",
|
||||
"expo-env.d.ts"
|
||||
]
|
||||
"include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user