Displaying Chip value contribution #6 #33
4
.env.example
Normal file
4
.env.example
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
EXPO_PUBLIC_API_URL=https://api.openai.com/v1/chat/completions
|
||||||
|
EXPO_PUBLIC_API_KEY=put-open-ai-key-here
|
||||||
|
EXPO_PUBLIC_MODEL_NAME=gpt-4o-mini
|
||||||
|
#EXPO_PUBLIC_MODEL_NAME=gpt-4-turbo # More expensive model, use sparingly
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -36,3 +36,6 @@ yarn-error.*
|
|||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
||||||
app-example
|
app-example
|
||||||
|
android
|
||||||
|
.env
|
||||||
|
coverage
|
18
README.md
18
README.md
@ -14,6 +14,24 @@ 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).
|
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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Open the `.env` file and add your OpenAI API key:
|
||||||
|
|
||||||
|
`EXPO_PUBLIC_API_KEY=put-open-ai-key-here`
|
||||||
|
|
||||||
|
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
|
### VSCode plugins
|
||||||
|
|
||||||
- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
|
- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
|
||||||
|
7
app.json
7
app.json
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"expo": {
|
"expo": {
|
||||||
"name": "poker-chips-helper",
|
"name": "Poker Chips Helper",
|
||||||
"slug": "poker-chips-helper",
|
"slug": "poker-chips-helper",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"orientation": "portrait",
|
"orientation": "portrait",
|
||||||
"icon": "./assets/images/icon.png",
|
"icon": "./assets/images/icon1.png",
|
||||||
"scheme": "myapp",
|
"scheme": "myapp",
|
||||||
"userInterfaceStyle": "automatic",
|
"userInterfaceStyle": "automatic",
|
||||||
"newArchEnabled": true,
|
"newArchEnabled": true,
|
||||||
@ -15,7 +15,8 @@
|
|||||||
"adaptiveIcon": {
|
"adaptiveIcon": {
|
||||||
"foregroundImage": "./assets/images/adaptive-icon.png",
|
"foregroundImage": "./assets/images/adaptive-icon.png",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
}
|
},
|
||||||
|
"package": "com.anonymous.pokerchipshelper"
|
||||||
},
|
},
|
||||||
"web": {
|
"web": {
|
||||||
"bundler": "metro",
|
"bundler": "metro",
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { Stack } from "expo-router";
|
import { Stack } from "expo-router";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
export default function RootLayout() {
|
const RootLayout: React.FC = () => (
|
||||||
return <Stack />;
|
<Stack screenOptions={{ headerShown: true, title: "Poker Chips Helper" }} />
|
||||||
}
|
);
|
||||||
|
export default RootLayout;
|
||||||
|
@ -4,6 +4,7 @@ import PlayerSelector from "@/components/PlayerSelector";
|
|||||||
import BuyInSelector from "@/components/BuyInSelector";
|
import BuyInSelector from "@/components/BuyInSelector";
|
||||||
import ChipsSelector from "@/components/ChipsSelector";
|
import ChipsSelector from "@/components/ChipsSelector";
|
||||||
import ChipDistributionSummary from "@/components/ChipDistributionSummary";
|
import ChipDistributionSummary from "@/components/ChipDistributionSummary";
|
||||||
|
import ChipDetection from "@/components/ChipDetection";
|
||||||
|
|
||||||
export enum COLORS {
|
export enum COLORS {
|
||||||
"white",
|
"white",
|
||||||
@ -13,11 +14,12 @@ export enum COLORS {
|
|||||||
"black",
|
"black",
|
||||||
}
|
}
|
||||||
|
|
||||||
const IndexScreen = () => {
|
const IndexScreen: React.FC = () => {
|
||||||
const [playerCount, setPlayerCount] = useState(2);
|
const [playerCount, setPlayerCount] = useState(2);
|
||||||
const [buyInAmount, setBuyInAmount] = useState<number>(20);
|
const [buyInAmount, setBuyInAmount] = useState<number>(20);
|
||||||
const [numberOfChips, setNumberOfChips] = useState<number>(5);
|
const [numberOfChips, setNumberOfChips] = useState<number>(5);
|
||||||
const [totalChipsCount, setTotalChipsCount] = useState<number[]>([]);
|
const [totalChipsCount, setTotalChipsCount] = useState<number[]>([]);
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
if (buyInAmount === null) {
|
if (buyInAmount === null) {
|
||||||
Alert.alert("Error", "Please select a valid buy-in amount");
|
Alert.alert("Error", "Please select a valid buy-in amount");
|
||||||
@ -28,16 +30,24 @@ 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 (
|
return (
|
||||||
<ScrollView contentContainerStyle={{ padding: 20, flexGrow: 1 }}>
|
<ScrollView contentContainerStyle={{ padding: 20, flexGrow: 1 }}>
|
||||||
<Text style={{ fontSize: 24, marginBottom: 30, marginTop: 50 }}>
|
|
||||||
Poker Chip Helper
|
|
||||||
</Text>
|
|
||||||
<PlayerSelector
|
<PlayerSelector
|
||||||
playerCount={playerCount}
|
playerCount={playerCount}
|
||||||
setPlayerCount={setPlayerCount}
|
setPlayerCount={setPlayerCount}
|
||||||
/>
|
/>
|
||||||
<BuyInSelector setBuyInAmount={setBuyInAmount} />
|
<BuyInSelector setBuyInAmount={setBuyInAmount} />
|
||||||
|
<ChipDetection updateChipCount={updateChipCount} />
|
||||||
<ChipsSelector
|
<ChipsSelector
|
||||||
totalChipsCount={totalChipsCount}
|
totalChipsCount={totalChipsCount}
|
||||||
setTotalChipsCount={setTotalChipsCount}
|
setTotalChipsCount={setTotalChipsCount}
|
||||||
@ -57,4 +67,5 @@ const IndexScreen = () => {
|
|||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IndexScreen;
|
export default IndexScreen;
|
||||||
|
BIN
assets/images/icon1.png
Normal file
BIN
assets/images/icon1.png
Normal file
Binary file not shown.
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
131
components/ChipDetection.tsx
Normal file
131
components/ChipDetection.tsx
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Button,
|
||||||
|
Image,
|
||||||
|
ActivityIndicator,
|
||||||
|
Text,
|
||||||
|
ScrollView,
|
||||||
|
} from "react-native";
|
||||||
|
import * as ImagePicker from "expo-image-picker";
|
||||||
|
|
||||||
|
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(process.env.EXPO_PUBLIC_API_URL, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${process.env.EXPO_PUBLIC_API_KEY}`, // Use environment variable for API key
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
model: process.env.EXPO_PUBLIC_MODEL_NAME, // Use environment variable for 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);
|
||||||
|
|
||||||
|
const filteredData = Object.fromEntries(
|
||||||
|
Object.entries(parsedData).filter(([_, count]) => count > 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
setLastDetectedChips(filteredData);
|
||||||
|
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;
|
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,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
1800
package-lock.json
generated
1800
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
78
package.json
78
package.json
@ -14,51 +14,57 @@
|
|||||||
"jest": {
|
"jest": {
|
||||||
"preset": "jest-expo"
|
"preset": "jest-expo"
|
||||||
},
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.0.0 <23.0.0"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^14.0.2",
|
"@expo/vector-icons": "14.0.4",
|
||||||
"@react-navigation/bottom-tabs": "^7.2.0",
|
"@react-navigation/bottom-tabs": "7.2.0",
|
||||||
"@react-navigation/native": "^7.0.14",
|
"@react-navigation/native": "7.0.14",
|
||||||
"expo": "~52.0.31",
|
"expo": "52.0.37",
|
||||||
"expo-blur": "~14.0.3",
|
"expo-blur": "14.0.3",
|
||||||
"expo-constants": "~17.0.5",
|
"expo-constants": "17.0.7",
|
||||||
"expo-font": "~13.0.3",
|
"expo-file-system": "18.0.11",
|
||||||
"expo-haptics": "~14.0.1",
|
"expo-font": "13.0.4",
|
||||||
"expo-linking": "~7.0.5",
|
"expo-haptics": "14.0.1",
|
||||||
"expo-router": "~4.0.17",
|
"expo-image-picker": "16.0.6",
|
||||||
"expo-splash-screen": "~0.29.21",
|
"expo-linking": "7.0.5",
|
||||||
"expo-status-bar": "~2.0.1",
|
"expo-router": "4.0.17",
|
||||||
"expo-symbols": "~0.2.2",
|
"expo-splash-screen": "0.29.22",
|
||||||
"expo-system-ui": "~4.0.8",
|
"expo-status-bar": "2.0.1",
|
||||||
"expo-web-browser": "~14.0.2",
|
"expo-symbols": "0.2.2",
|
||||||
|
"expo-system-ui": "4.0.8",
|
||||||
|
"expo-web-browser": "14.0.2",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"react-native": "0.76.7",
|
"react-native": "0.76.7",
|
||||||
"react-native-gesture-handler": "~2.20.2",
|
"react-native-gesture-handler": "2.20.2",
|
||||||
"react-native-reanimated": "~3.16.1",
|
"react-native-reanimated": "3.16.7",
|
||||||
"react-native-safe-area-context": "4.12.0",
|
"react-native-safe-area-context": "4.12.0",
|
||||||
"react-native-screens": "~4.4.0",
|
"react-native-screens": "4.4.0",
|
||||||
"react-native-web": "~0.19.13",
|
"react-native-web": "0.19.13",
|
||||||
"react-native-webview": "13.12.5"
|
"react-native-webview": "13.12.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "7.26.9",
|
||||||
"@testing-library/dom": "^10.4.0",
|
"@testing-library/dom": "10.4.0",
|
||||||
"@testing-library/jest-native": "^5.4.3",
|
"@testing-library/jest-native": "5.4.3",
|
||||||
"@testing-library/react": "^16.2.0",
|
"@testing-library/react": "16.2.0",
|
||||||
"@testing-library/react-native": "^13.0.1",
|
"@testing-library/react-native": "13.0.1",
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "29.5.14",
|
||||||
"@types/react": "~18.3.12",
|
"@types/react": "18.3.12",
|
||||||
"@types/react-test-renderer": "^18.3.0",
|
"@types/react-test-renderer": "18.3.0",
|
||||||
"eslint": "^9.20.0",
|
"eslint": "9.21.0",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "10.0.1",
|
||||||
"eslint-plugin-prettier": "^5.2.3",
|
"eslint-plugin-prettier": "5.2.3",
|
||||||
"eslint-plugin-react": "^7.37.4",
|
"eslint-plugin-react": "7.37.4",
|
||||||
"eslint-plugin-react-native": "^5.0.0",
|
"eslint-plugin-react-native": "5.0.0",
|
||||||
"jest": "^29.2.1",
|
"jest": "29.7.0",
|
||||||
"jest-expo": "~52.0.3",
|
"jest-expo": "52.0.5",
|
||||||
"prettier": "^3.4.2",
|
"jest-fetch-mock": "3.0.3",
|
||||||
|
"prettier": "3.5.2",
|
||||||
"react-test-renderer": "18.3.1",
|
"react-test-renderer": "18.3.1",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "5.7.3"
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,14 @@
|
|||||||
{
|
{
|
||||||
"extends": "expo/tsconfig.base",
|
"extends": "expo/tsconfig.base",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"jsx": "react",
|
"jsx": "react-native", // Update this to "react-native" for React Native compatibility
|
||||||
"strict": true,
|
"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": {
|
"paths": {
|
||||||
"@/*": [
|
"@/*": ["./*"]
|
||||||
"./*"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
|
||||||
"**/*.ts",
|
|
||||||
"**/*.tsx",
|
|
||||||
".expo/types/**/*.ts",
|
|
||||||
"expo-env.d.ts"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user