diff --git a/components/BottomNav.js b/components/BottomNav.js index 8724097..a7e2486 100644 --- a/components/BottomNav.js +++ b/components/BottomNav.js @@ -11,6 +11,7 @@ import { Home } from "../pages/Home"; import { Settings } from "../pages/Settings"; import { AboutLefty } from "../subpages/AboutLefty"; import { CameraScan } from "../subpages/CameraScan"; +import { CameraLanding } from "../subpages/CameraLanding"; const Tab = createBottomTabNavigator(); const Stack = createNativeStackNavigator(); @@ -99,6 +100,11 @@ export function BottomNav() { component={CameraScan} options={{ headerShown: false }} /> + // ); diff --git a/components/PaginationNav.js b/components/PaginationNav.js index 23ad14d..5b0e538 100644 --- a/components/PaginationNav.js +++ b/components/PaginationNav.js @@ -1,5 +1,5 @@ -import { View, Text } from "react-native"; -import { IconButton } from "react-native-paper"; +import { View } from "react-native"; +import { IconButton, Text } from "react-native-paper"; function PaginationNav({ currentPageNumber, goToPage, maxPageNumber }) { const deltaPage = (delta) => goToPage(parseInt(currentPageNumber) + delta); diff --git a/subpages/CameraLanding.js b/subpages/CameraLanding.js new file mode 100644 index 0000000..542f985 --- /dev/null +++ b/subpages/CameraLanding.js @@ -0,0 +1,101 @@ +import React from "react"; +import { SafeAreaView, ScrollView, View } from "react-native"; +import { Button, Text, useTheme } from "react-native-paper"; +import { Icon, Slider } from "@rneui/themed"; + +import Layout from "../components/Layout"; + +export const CameraLanding = ({ navigation, route }) => { + const theme = useTheme(); + + const [value, setValue] = React.useState(80); + + const { currIngredients, newIngredients } = route.params; + + return ( + + navigation.goBack()} + > + + + Use the slider below to adjust the sensitivity! + + + + ), + }} + /> + Sensitivity: {value}% + + {newIngredients.map((ingredient, index) => ( + = value / 100 + ? theme.colors.font + : theme.colors.secondary, + }} + variant="bodyLarge" + > + {index + 1}. {ingredient.name} + + ))} + + + + + + + ); +}; diff --git a/subpages/CameraScan.js b/subpages/CameraScan.js index 129c742..9c5708b 100644 --- a/subpages/CameraScan.js +++ b/subpages/CameraScan.js @@ -1,17 +1,23 @@ import { useEffect, useState } from "react"; import { Dimensions, Linking, SafeAreaView, View } from "react-native"; import { Button, Text, useTheme } from "react-native-paper"; +import { Icon } from "@rneui/themed"; + import * as ImagePicker from "expo-image-picker"; -import { BACKEND_API_URL } from "../config/config"; -import { useAxios } from "../providers/hooks"; -import Layout from "../components/Layout"; +import { BACKEND_API_URL, GOOGLE_API_KEY } from "../config/config"; +import { useAxios, useNotification } from "../providers/hooks"; import { LoadingIcon } from "../components/LoadingIcon"; +import Layout from "../components/Layout"; -export function CameraScan({ navigation }) { +export function CameraScan({ navigation, route }) { const theme = useTheme(); const { publicAxios } = useAxios(); + const { showNotification } = useNotification(); const [isLoading, setIsLoading] = useState(false); + const [isCameraLoading, setIsCameraLoading] = useState(false); + const [isMediaLoading, setIsMediaLoading] = useState(false); + const { currIngredients } = route.params; const windowHeight = Dimensions.get("window").height; @@ -30,48 +36,106 @@ export function CameraScan({ navigation }) { requestPermissions(); }, []); + function handleNoIngredients() { + showNotification({ + title: "No ingredients found", + description: "Could not find any ingredients in picture", + type: "warn", + }); + setIsLoading(false); + } + async function handlePicture(result) { setIsLoading(true); + + const googleVisionRes = await fetch( + `https://vision.googleapis.com/v1/images:annotate?key=${GOOGLE_API_KEY}`, + { + method: "POST", + body: JSON.stringify({ + requests: [ + { + image: { + content: result.base64, + }, + features: [{ type: "LABEL_DETECTION", maxResults: 15 }], + }, + ], + }), + } + ); + + const res = await googleVisionRes.json(); + + const detectedIngredients = res.responses[0].labelAnnotations.map( + (annotation) => ({ + name: annotation.description, + score: annotation.score, + }) + ); + + if (detectedIngredients.length === 0) { + return handleNoIngredients(); + } + await publicAxios .post(`${BACKEND_API_URL}/ingredients`, { - base64_string: result.base64, + ingredients: detectedIngredients, + }) + .then((res) => { + if (res.length === 0) { + return handleNoIngredients(); + } else { + navigation.navigate("CameraLanding", { + newIngredients: res, + currIngredients: currIngredients, + }); + } }) - .then((res) => - navigation.navigate("Recipe", { - ingredients: res.data - .filter((ingredient) => ingredient.confidence > 75) - .map((ingredient) => ingredient.name), - }) - ) .catch((error) => {}); - setIsLoading(false); } - async function takeImage() { - setIsLoading(true); - let result = await ImagePicker.launchCameraAsync({ + function takeImage() { + setIsCameraLoading(true); + ImagePicker.launchCameraAsync({ base64: true, quality: 1, - }); - setIsLoading(false); - - if (!result.canceled) { - await handlePicture(result.assets[0]); - } + }) + .then((res) => { + if (!res.canceled) { + handlePicture(res.assets[0]); + } + }) + .catch((error) => + showNotification({ + title: "Failed to open camera", + description: error.message, + type: "error", + }) + ) + .finally(() => setIsCameraLoading(false)); } - async function pickImage() { - setIsLoading(true); - let result = await ImagePicker.launchImageLibraryAsync({ + function pickImage() { + setIsMediaLoading(true); + ImagePicker.launchImageLibraryAsync({ base64: true, quality: 1, - }); - setIsLoading(false); - - if (!result.canceled) { - await handlePicture(result.assets[0]); - } + }) + .then((res) => { + if (!res.canceled) { + handlePicture(res.assets[0]); + } + }) + .catch((error) => + showNotification({ + title: "Failed to open image library", + description: error.message, + type: "error", + }) + ) + .finally(() => setIsMediaLoading(false)); } return ( @@ -81,24 +145,67 @@ export function CameraScan({ navigation }) { onAction={() => navigation.goBack()} iconName="chevron-left" > - - {isLoading && } - Upload or take a picture - - - + {isLoading ? ( + + + + ) : ( + + + + + + + Upload or take a picture + + + + )} );