Skip to content

Commit

Permalink
Add sensitivity selection slider
Browse files Browse the repository at this point in the history
  • Loading branch information
ruishanteo committed Oct 8, 2023
1 parent 471ca1f commit 3f54f2e
Show file tree
Hide file tree
Showing 4 changed files with 265 additions and 51 deletions.
6 changes: 6 additions & 0 deletions components/BottomNav.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -99,6 +100,11 @@ export function BottomNav() {
component={CameraScan}
options={{ headerShown: false }}
/>
<Stack.Screen
name="CameraLanding"
component={CameraLanding}
options={{ headerShown: false }}
/>
</Stack.Navigator>
//
);
Expand Down
4 changes: 2 additions & 2 deletions components/PaginationNav.js
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
101 changes: 101 additions & 0 deletions subpages/CameraLanding.js
Original file line number Diff line number Diff line change
@@ -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 (
<SafeAreaView style={{ backgroundColor: theme.colors.tertiary }}>
<Layout
title="Ingredients Found"
iconName="chevron-left"
onAction={() => navigation.goBack()}
>
<View
style={{
padding: 20,
gap: 10,
backgroundColor: theme.colors.background,
height: "100%",
}}
>
<Text variant="bodyLarge">
Use the slider below to adjust the sensitivity!
</Text>

<Slider
value={value}
onValueChange={setValue}
maximumValue={100}
minimumValue={0}
step={1}
allowTouchTrack
minimumTrackTintColor={theme.colors.primary}
maximumTrackTintColor={theme.colors.tertiary}
thumbStyle={{
height: 20,
width: 20,
}}
thumbProps={{
children: (
<Icon
name="food"
type="material-community"
size={20}
reverse
containerStyle={{
bottom: 20,
right: 20,
}}
color={theme.colors.primary}
/>
),
}}
/>
<Text>Sensitivity: {value}%</Text>
<View style={{ height: "60%", marginTop: 15, gap: 8 }}>
{newIngredients.map((ingredient, index) => (
<Text
key={index}
numberOfLines={1}
style={{
color:
ingredient.score >= value / 100
? theme.colors.font
: theme.colors.secondary,
}}
variant="bodyLarge"
>
{index + 1}. {ingredient.name}
</Text>
))}
</View>

<Button
onPress={() => {
const addedIngredients = newIngredients
.filter((ingredient) => ingredient.score >= value / 100)
.map((ingredient) => ingredient.name);
navigation.navigate("Recipe", {
ingredients: [...currIngredients, ...addedIngredients],
});
}}
mode="contained"
style={{ alignSelf: "center", width: 300, marginTop: 50 }}
icon="shaker"
>
Confirm
</Button>
</View>
</Layout>
</SafeAreaView>
);
};
205 changes: 156 additions & 49 deletions subpages/CameraScan.js
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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 (
Expand All @@ -81,24 +145,67 @@ export function CameraScan({ navigation }) {
onAction={() => navigation.goBack()}
iconName="chevron-left"
>
<View
style={{
backgroundColor: theme.colors.background,
height: windowHeight - 150,
justifyContent: "center",
alignItems: "center",
gap: 20,
}}
>
{isLoading && <LoadingIcon />}
<Text variant="labelLarge">Upload or take a picture</Text>
<Button onPress={pickImage} disabled={isLoading} mode="contained">
Upload from library
</Button>
<Button onPress={takeImage} disabled={isLoading} mode="contained">
Take picture
</Button>
</View>
{isLoading ? (
<View
style={{ height: "100%", backgroundColor: theme.colors.background }}
>
<LoadingIcon fullSize={true} styles={{ marginTop: -100 }} />
</View>
) : (
<View
style={{
backgroundColor: theme.colors.background,
height: windowHeight - 150,
justifyContent: "center",
alignItems: "center",
gap: 20,
}}
>
<View style={{ flexDirection: "row" }}>
<Icon
name="noodles"
type="material-community"
size={100}
color={theme.colors.primary}
/>
<Icon
name="emoji-food-beverage"
type="material-icon"
size={100}
color={theme.colors.primary}
/>
<Icon
name="food-variant"
type="material-community"
size={100}
color={theme.colors.primary}
/>
</View>
<Text variant="bodyLarge">Upload or take a picture</Text>
<Button
onPress={pickImage}
disabled={isLoading || isCameraLoading || isMediaLoading}
loading={isMediaLoading}
mode="contained"
style={{ width: 200, backgroundColor: theme.colors.secondary }}
contentStyle={{ flexDirection: "row-reverse" }}
icon="food-croissant"
>
Upload from library
</Button>
<Button
onPress={takeImage}
disabled={isLoading || isCameraLoading || isMediaLoading}
loading={isCameraLoading}
mode="contained"
style={{ width: 200, backgroundColor: theme.colors.secondary }}
contentStyle={{ flexDirection: "row-reverse" }}
icon="hamburger"
>
Take picture
</Button>
</View>
)}
</Layout>
</SafeAreaView>
);
Expand Down

0 comments on commit 3f54f2e

Please sign in to comment.