scan food
This commit is contained in:
parent
ca64a100b6
commit
21afb085e3
Binary file not shown.
@ -5,6 +5,7 @@ import {
|
||||
KeyboardAvoidingView,
|
||||
Modal,
|
||||
Platform,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
@ -244,166 +245,184 @@ export function ScanFoodModal({
|
||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||
style={styles.resultContainer}
|
||||
>
|
||||
<View style={styles.resultHeader}>
|
||||
<TouchableOpacity onPress={onClose} style={styles.closeButton}>
|
||||
<Ionicons name="close" size={28} color={theme.colors.gray900} />
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.resultTitle}>
|
||||
{notFound ? "Barcode Not Found" : "Food Details"}
|
||||
</Text>
|
||||
<View style={{ width: 28 }} />
|
||||
</View>
|
||||
|
||||
<View style={styles.foodCard}>
|
||||
{!notFound && foodData ? (
|
||||
<>
|
||||
<View style={styles.foodIconContainer}>
|
||||
<LinearGradient
|
||||
colors={theme.gradients.success}
|
||||
style={styles.foodIcon}
|
||||
>
|
||||
<Ionicons name="restaurant" size={32} color="#fff" />
|
||||
</LinearGradient>
|
||||
</View>
|
||||
|
||||
<Text style={styles.foodName}>{foodData.name}</Text>
|
||||
{foodData.brand ? (
|
||||
<Text style={styles.servingSize}>{foodData.brand}</Text>
|
||||
) : null}
|
||||
<Text style={styles.servingSize}>{foodData.servingSize}</Text>
|
||||
|
||||
<View style={styles.caloriesBadge}>
|
||||
<Text style={styles.caloriesValue}>
|
||||
{foodData.caloriesPerServing}
|
||||
</Text>
|
||||
<Text style={styles.caloriesLabel}>kcal per serving</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.servingsContainer}>
|
||||
<Text style={styles.label}>Number of Servings</Text>
|
||||
<View style={styles.servingsInput}>
|
||||
<TouchableOpacity
|
||||
onPress={() =>
|
||||
setServings(
|
||||
String(
|
||||
Math.max(0.5, parseFloat(servings || "1") - 0.5),
|
||||
),
|
||||
)
|
||||
}
|
||||
style={styles.servingsButton}
|
||||
>
|
||||
<Ionicons
|
||||
name="remove"
|
||||
size={20}
|
||||
color={theme.colors.primary}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<TextInput
|
||||
style={styles.servingsValue}
|
||||
value={servings}
|
||||
onChangeText={setServings}
|
||||
keyboardType="decimal-pad"
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={() =>
|
||||
setServings(String(parseFloat(servings || "1") + 0.5))
|
||||
}
|
||||
style={styles.servingsButton}
|
||||
>
|
||||
<Ionicons
|
||||
name="add"
|
||||
size={20}
|
||||
color={theme.colors.primary}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Text style={styles.servingSize}>
|
||||
We could not find this product in OpenFoodFacts.
|
||||
</Text>
|
||||
|
||||
<Text style={styles.label}>Food Name</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
value={manualFoodName}
|
||||
onChangeText={setManualFoodName}
|
||||
placeholder="Enter food name"
|
||||
placeholderTextColor={theme.colors.gray400}
|
||||
<View style={styles.resultSheet}>
|
||||
<View style={styles.resultHeader}>
|
||||
<TouchableOpacity
|
||||
onPress={handleReset}
|
||||
style={styles.closeButton}
|
||||
>
|
||||
<Ionicons
|
||||
name="scan"
|
||||
size={24}
|
||||
color={theme.colors.gray700}
|
||||
/>
|
||||
|
||||
<Text style={styles.label}>Calories</Text>
|
||||
<View style={styles.caloriesInputContainer}>
|
||||
<TextInput
|
||||
style={[styles.input, styles.caloriesInput]}
|
||||
value={manualCalories}
|
||||
onChangeText={setManualCalories}
|
||||
keyboardType="number-pad"
|
||||
placeholder="0"
|
||||
placeholderTextColor={theme.colors.gray400}
|
||||
/>
|
||||
<Text style={styles.caloriesUnit}>kcal</Text>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
|
||||
<View style={styles.mealTypeContainer}>
|
||||
<Text style={styles.label}>Meal Type</Text>
|
||||
<View style={styles.mealTypeRow}>
|
||||
{MEAL_TYPES.map((type) => {
|
||||
const active = mealType === type;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={type}
|
||||
onPress={() => setMealType(type)}
|
||||
style={[
|
||||
styles.mealTypeChip,
|
||||
active && styles.mealTypeChipActive,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.mealTypeText,
|
||||
active && styles.mealTypeTextActive,
|
||||
]}
|
||||
>
|
||||
{type.charAt(0).toUpperCase() + type.slice(1)}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.resultTitle}>
|
||||
{notFound ? "Barcode Not Found" : "Food Details"}
|
||||
</Text>
|
||||
<View style={{ width: 24 }} />
|
||||
</View>
|
||||
|
||||
<View style={styles.totalCalories}>
|
||||
<ScrollView
|
||||
style={styles.resultScroll}
|
||||
contentContainerStyle={styles.resultScrollContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<View style={styles.foodCard}>
|
||||
{!notFound && foodData ? (
|
||||
<>
|
||||
<View style={styles.foodIconContainer}>
|
||||
<LinearGradient
|
||||
colors={theme.gradients.success}
|
||||
style={styles.foodIcon}
|
||||
>
|
||||
<Ionicons name="restaurant" size={28} color="#fff" />
|
||||
</LinearGradient>
|
||||
</View>
|
||||
|
||||
<Text style={styles.foodName}>{foodData.name}</Text>
|
||||
<Text style={styles.servingMeta}>
|
||||
{[foodData.brand, foodData.servingSize]
|
||||
.filter(Boolean)
|
||||
.join(" • ")}
|
||||
</Text>
|
||||
|
||||
<View style={styles.caloriesBadge}>
|
||||
<Text style={styles.caloriesValue}>
|
||||
{foodData.caloriesPerServing}
|
||||
</Text>
|
||||
<Text style={styles.caloriesLabel}>
|
||||
kcal per serving
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.servingsContainer}>
|
||||
<Text style={styles.label}>Servings</Text>
|
||||
<View style={styles.servingsInput}>
|
||||
<TouchableOpacity
|
||||
onPress={() =>
|
||||
setServings(
|
||||
String(
|
||||
Math.max(
|
||||
0.5,
|
||||
parseFloat(servings || "1") - 0.5,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
style={styles.servingsButton}
|
||||
>
|
||||
<Ionicons
|
||||
name="remove"
|
||||
size={20}
|
||||
color={theme.colors.primary}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<TextInput
|
||||
style={styles.servingsValue}
|
||||
value={servings}
|
||||
onChangeText={setServings}
|
||||
keyboardType="decimal-pad"
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={() =>
|
||||
setServings(
|
||||
String(parseFloat(servings || "1") + 0.5),
|
||||
)
|
||||
}
|
||||
style={styles.servingsButton}
|
||||
>
|
||||
<Ionicons
|
||||
name="add"
|
||||
size={20}
|
||||
color={theme.colors.primary}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Text style={styles.servingMeta}>
|
||||
We could not find this product in OpenFoodFacts.
|
||||
</Text>
|
||||
|
||||
<Text style={styles.label}>Food Name</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
value={manualFoodName}
|
||||
onChangeText={setManualFoodName}
|
||||
placeholder="Enter food name"
|
||||
placeholderTextColor={theme.colors.gray400}
|
||||
/>
|
||||
|
||||
<Text style={styles.label}>Calories</Text>
|
||||
<View style={styles.caloriesInputContainer}>
|
||||
<TextInput
|
||||
style={[styles.input, styles.caloriesInput]}
|
||||
value={manualCalories}
|
||||
onChangeText={setManualCalories}
|
||||
keyboardType="number-pad"
|
||||
placeholder="0"
|
||||
placeholderTextColor={theme.colors.gray400}
|
||||
/>
|
||||
<Text style={styles.caloriesUnit}>kcal</Text>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
|
||||
<View style={styles.mealTypeContainer}>
|
||||
<Text style={styles.label}>Meal Type</Text>
|
||||
<View style={styles.mealTypeRow}>
|
||||
{MEAL_TYPES.map((type) => {
|
||||
const active = mealType === type;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={type}
|
||||
onPress={() => setMealType(type)}
|
||||
style={[
|
||||
styles.mealTypeChip,
|
||||
active && styles.mealTypeChipActive,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.mealTypeText,
|
||||
active && styles.mealTypeTextActive,
|
||||
]}
|
||||
>
|
||||
{type.charAt(0).toUpperCase() + type.slice(1)}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
<View style={styles.footerSummary}>
|
||||
<Text style={styles.totalLabel}>Total Calories</Text>
|
||||
<Text style={styles.totalValue}>{getTotalCalories()} kcal</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.buttonRow}>
|
||||
<TouchableOpacity
|
||||
onPress={handleReset}
|
||||
style={styles.rescanButton}
|
||||
>
|
||||
<Text style={styles.rescanButtonText}>Scan Again</Text>
|
||||
</TouchableOpacity>
|
||||
<View style={styles.buttonRow}>
|
||||
<TouchableOpacity onPress={onClose} style={styles.rejectButton}>
|
||||
<Text style={styles.rejectButtonText}>Reject</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={handleConfirm}
|
||||
style={styles.confirmButtonContainer}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={theme.gradients.success}
|
||||
style={styles.confirmButton}
|
||||
<TouchableOpacity
|
||||
onPress={handleConfirm}
|
||||
style={styles.confirmButtonContainer}
|
||||
>
|
||||
<Text style={styles.confirmButtonText}>
|
||||
{notFound ? "Add Manual" : "Add to Diary"}
|
||||
</Text>
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
<LinearGradient
|
||||
colors={theme.gradients.success}
|
||||
style={styles.confirmButton}
|
||||
>
|
||||
<Text style={styles.confirmButtonText}>Add to Count</Text>
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
)}
|
||||
@ -463,15 +482,32 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
resultContainer: {
|
||||
flex: 1,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.35)",
|
||||
justifyContent: "flex-end",
|
||||
},
|
||||
resultSheet: {
|
||||
backgroundColor: theme.colors.background,
|
||||
borderTopLeftRadius: 24,
|
||||
borderTopRightRadius: 24,
|
||||
maxHeight: "80%",
|
||||
},
|
||||
resultHeader: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
padding: 20,
|
||||
paddingTop: 60,
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 14,
|
||||
backgroundColor: "#fff",
|
||||
borderTopLeftRadius: 24,
|
||||
borderTopRightRadius: 24,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: theme.colors.gray100,
|
||||
},
|
||||
resultScroll: {
|
||||
flexGrow: 0,
|
||||
},
|
||||
resultScrollContent: {
|
||||
paddingBottom: 12,
|
||||
},
|
||||
resultTitle: {
|
||||
fontSize: 18,
|
||||
@ -479,46 +515,47 @@ const styles = StyleSheet.create({
|
||||
color: theme.colors.gray900,
|
||||
},
|
||||
foodCard: {
|
||||
margin: 20,
|
||||
padding: 24,
|
||||
marginHorizontal: 16,
|
||||
marginTop: 12,
|
||||
padding: 16,
|
||||
backgroundColor: "#fff",
|
||||
borderRadius: 24,
|
||||
borderRadius: 16,
|
||||
alignItems: "center",
|
||||
...theme.shadows.medium,
|
||||
},
|
||||
foodIconContainer: {
|
||||
marginBottom: 16,
|
||||
marginBottom: 10,
|
||||
},
|
||||
foodIcon: {
|
||||
width: 80,
|
||||
height: 80,
|
||||
borderRadius: 40,
|
||||
width: 64,
|
||||
height: 64,
|
||||
borderRadius: 32,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
foodName: {
|
||||
fontSize: 24,
|
||||
fontSize: 20,
|
||||
fontWeight: "700",
|
||||
color: theme.colors.gray900,
|
||||
marginBottom: 8,
|
||||
marginBottom: 4,
|
||||
textAlign: "center",
|
||||
},
|
||||
servingSize: {
|
||||
fontSize: 16,
|
||||
servingMeta: {
|
||||
fontSize: 14,
|
||||
color: theme.colors.gray600,
|
||||
marginBottom: 16,
|
||||
marginBottom: 12,
|
||||
textAlign: "center",
|
||||
},
|
||||
caloriesBadge: {
|
||||
backgroundColor: theme.colors.gray50,
|
||||
paddingHorizontal: 24,
|
||||
paddingVertical: 16,
|
||||
borderRadius: 16,
|
||||
paddingHorizontal: 18,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 12,
|
||||
alignItems: "center",
|
||||
marginBottom: 24,
|
||||
marginBottom: 14,
|
||||
},
|
||||
caloriesValue: {
|
||||
fontSize: 32,
|
||||
fontSize: 26,
|
||||
fontWeight: "700",
|
||||
color: theme.colors.primary,
|
||||
},
|
||||
@ -529,11 +566,11 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
servingsContainer: {
|
||||
width: "100%",
|
||||
marginBottom: 24,
|
||||
marginBottom: 14,
|
||||
},
|
||||
mealTypeContainer: {
|
||||
width: "100%",
|
||||
marginBottom: 24,
|
||||
marginBottom: 6,
|
||||
},
|
||||
mealTypeRow: {
|
||||
flexDirection: "row",
|
||||
@ -615,13 +652,18 @@ const styles = StyleSheet.create({
|
||||
color: theme.colors.gray500,
|
||||
},
|
||||
totalCalories: {
|
||||
width: "100%",
|
||||
display: "none",
|
||||
},
|
||||
footerSummary: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
paddingTop: 24,
|
||||
paddingHorizontal: 20,
|
||||
paddingTop: 10,
|
||||
paddingBottom: 10,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: theme.colors.gray200,
|
||||
backgroundColor: "#fff",
|
||||
},
|
||||
totalLabel: {
|
||||
fontSize: 16,
|
||||
@ -635,17 +677,20 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
buttonRow: {
|
||||
flexDirection: "row",
|
||||
padding: 20,
|
||||
paddingHorizontal: 20,
|
||||
paddingTop: 8,
|
||||
paddingBottom: 16,
|
||||
gap: 12,
|
||||
backgroundColor: "#fff",
|
||||
},
|
||||
rescanButton: {
|
||||
rejectButton: {
|
||||
flex: 1,
|
||||
paddingVertical: 16,
|
||||
borderRadius: 20,
|
||||
backgroundColor: theme.colors.gray100,
|
||||
alignItems: "center",
|
||||
},
|
||||
rescanButtonText: {
|
||||
rejectButtonText: {
|
||||
fontSize: 16,
|
||||
fontWeight: "700",
|
||||
color: theme.colors.gray700,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user