scan food

This commit is contained in:
echo 2026-03-31 16:55:55 +02:00
parent ca64a100b6
commit 21afb085e3
2 changed files with 222 additions and 177 deletions

Binary file not shown.

View File

@ -5,6 +5,7 @@ import {
KeyboardAvoidingView, KeyboardAvoidingView,
Modal, Modal,
Platform, Platform,
ScrollView,
StyleSheet, StyleSheet,
Text, Text,
TextInput, TextInput,
@ -244,16 +245,29 @@ export function ScanFoodModal({
behavior={Platform.OS === "ios" ? "padding" : "height"} behavior={Platform.OS === "ios" ? "padding" : "height"}
style={styles.resultContainer} style={styles.resultContainer}
> >
<View style={styles.resultSheet}>
<View style={styles.resultHeader}> <View style={styles.resultHeader}>
<TouchableOpacity onPress={onClose} style={styles.closeButton}> <TouchableOpacity
<Ionicons name="close" size={28} color={theme.colors.gray900} /> onPress={handleReset}
style={styles.closeButton}
>
<Ionicons
name="scan"
size={24}
color={theme.colors.gray700}
/>
</TouchableOpacity> </TouchableOpacity>
<Text style={styles.resultTitle}> <Text style={styles.resultTitle}>
{notFound ? "Barcode Not Found" : "Food Details"} {notFound ? "Barcode Not Found" : "Food Details"}
</Text> </Text>
<View style={{ width: 28 }} /> <View style={{ width: 24 }} />
</View> </View>
<ScrollView
style={styles.resultScroll}
contentContainerStyle={styles.resultScrollContent}
showsVerticalScrollIndicator={false}
>
<View style={styles.foodCard}> <View style={styles.foodCard}>
{!notFound && foodData ? ( {!notFound && foodData ? (
<> <>
@ -262,31 +276,37 @@ export function ScanFoodModal({
colors={theme.gradients.success} colors={theme.gradients.success}
style={styles.foodIcon} style={styles.foodIcon}
> >
<Ionicons name="restaurant" size={32} color="#fff" /> <Ionicons name="restaurant" size={28} color="#fff" />
</LinearGradient> </LinearGradient>
</View> </View>
<Text style={styles.foodName}>{foodData.name}</Text> <Text style={styles.foodName}>{foodData.name}</Text>
{foodData.brand ? ( <Text style={styles.servingMeta}>
<Text style={styles.servingSize}>{foodData.brand}</Text> {[foodData.brand, foodData.servingSize]
) : null} .filter(Boolean)
<Text style={styles.servingSize}>{foodData.servingSize}</Text> .join(" • ")}
</Text>
<View style={styles.caloriesBadge}> <View style={styles.caloriesBadge}>
<Text style={styles.caloriesValue}> <Text style={styles.caloriesValue}>
{foodData.caloriesPerServing} {foodData.caloriesPerServing}
</Text> </Text>
<Text style={styles.caloriesLabel}>kcal per serving</Text> <Text style={styles.caloriesLabel}>
kcal per serving
</Text>
</View> </View>
<View style={styles.servingsContainer}> <View style={styles.servingsContainer}>
<Text style={styles.label}>Number of Servings</Text> <Text style={styles.label}>Servings</Text>
<View style={styles.servingsInput}> <View style={styles.servingsInput}>
<TouchableOpacity <TouchableOpacity
onPress={() => onPress={() =>
setServings( setServings(
String( String(
Math.max(0.5, parseFloat(servings || "1") - 0.5), Math.max(
0.5,
parseFloat(servings || "1") - 0.5,
),
), ),
) )
} }
@ -306,7 +326,9 @@ export function ScanFoodModal({
/> />
<TouchableOpacity <TouchableOpacity
onPress={() => onPress={() =>
setServings(String(parseFloat(servings || "1") + 0.5)) setServings(
String(parseFloat(servings || "1") + 0.5),
)
} }
style={styles.servingsButton} style={styles.servingsButton}
> >
@ -321,7 +343,7 @@ export function ScanFoodModal({
</> </>
) : ( ) : (
<> <>
<Text style={styles.servingSize}> <Text style={styles.servingMeta}>
We could not find this product in OpenFoodFacts. We could not find this product in OpenFoodFacts.
</Text> </Text>
@ -376,19 +398,17 @@ export function ScanFoodModal({
})} })}
</View> </View>
</View> </View>
</View>
</ScrollView>
<View style={styles.totalCalories}> <View style={styles.footerSummary}>
<Text style={styles.totalLabel}>Total Calories</Text> <Text style={styles.totalLabel}>Total Calories</Text>
<Text style={styles.totalValue}>{getTotalCalories()} kcal</Text> <Text style={styles.totalValue}>{getTotalCalories()} kcal</Text>
</View> </View>
</View>
<View style={styles.buttonRow}> <View style={styles.buttonRow}>
<TouchableOpacity <TouchableOpacity onPress={onClose} style={styles.rejectButton}>
onPress={handleReset} <Text style={styles.rejectButtonText}>Reject</Text>
style={styles.rescanButton}
>
<Text style={styles.rescanButtonText}>Scan Again</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity <TouchableOpacity
@ -399,12 +419,11 @@ export function ScanFoodModal({
colors={theme.gradients.success} colors={theme.gradients.success}
style={styles.confirmButton} style={styles.confirmButton}
> >
<Text style={styles.confirmButtonText}> <Text style={styles.confirmButtonText}>Add to Count</Text>
{notFound ? "Add Manual" : "Add to Diary"}
</Text>
</LinearGradient> </LinearGradient>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</View>
</KeyboardAvoidingView> </KeyboardAvoidingView>
)} )}
</View> </View>
@ -463,15 +482,32 @@ const styles = StyleSheet.create({
}, },
resultContainer: { resultContainer: {
flex: 1, flex: 1,
backgroundColor: "rgba(0, 0, 0, 0.35)",
justifyContent: "flex-end",
},
resultSheet: {
backgroundColor: theme.colors.background, backgroundColor: theme.colors.background,
borderTopLeftRadius: 24,
borderTopRightRadius: 24,
maxHeight: "80%",
}, },
resultHeader: { resultHeader: {
flexDirection: "row", flexDirection: "row",
justifyContent: "space-between", justifyContent: "space-between",
alignItems: "center", alignItems: "center",
padding: 20, paddingHorizontal: 20,
paddingTop: 60, paddingVertical: 14,
backgroundColor: "#fff", backgroundColor: "#fff",
borderTopLeftRadius: 24,
borderTopRightRadius: 24,
borderBottomWidth: 1,
borderBottomColor: theme.colors.gray100,
},
resultScroll: {
flexGrow: 0,
},
resultScrollContent: {
paddingBottom: 12,
}, },
resultTitle: { resultTitle: {
fontSize: 18, fontSize: 18,
@ -479,46 +515,47 @@ const styles = StyleSheet.create({
color: theme.colors.gray900, color: theme.colors.gray900,
}, },
foodCard: { foodCard: {
margin: 20, marginHorizontal: 16,
padding: 24, marginTop: 12,
padding: 16,
backgroundColor: "#fff", backgroundColor: "#fff",
borderRadius: 24, borderRadius: 16,
alignItems: "center", alignItems: "center",
...theme.shadows.medium, ...theme.shadows.medium,
}, },
foodIconContainer: { foodIconContainer: {
marginBottom: 16, marginBottom: 10,
}, },
foodIcon: { foodIcon: {
width: 80, width: 64,
height: 80, height: 64,
borderRadius: 40, borderRadius: 32,
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
}, },
foodName: { foodName: {
fontSize: 24, fontSize: 20,
fontWeight: "700", fontWeight: "700",
color: theme.colors.gray900, color: theme.colors.gray900,
marginBottom: 8, marginBottom: 4,
textAlign: "center", textAlign: "center",
}, },
servingSize: { servingMeta: {
fontSize: 16, fontSize: 14,
color: theme.colors.gray600, color: theme.colors.gray600,
marginBottom: 16, marginBottom: 12,
textAlign: "center", textAlign: "center",
}, },
caloriesBadge: { caloriesBadge: {
backgroundColor: theme.colors.gray50, backgroundColor: theme.colors.gray50,
paddingHorizontal: 24, paddingHorizontal: 18,
paddingVertical: 16, paddingVertical: 10,
borderRadius: 16, borderRadius: 12,
alignItems: "center", alignItems: "center",
marginBottom: 24, marginBottom: 14,
}, },
caloriesValue: { caloriesValue: {
fontSize: 32, fontSize: 26,
fontWeight: "700", fontWeight: "700",
color: theme.colors.primary, color: theme.colors.primary,
}, },
@ -529,11 +566,11 @@ const styles = StyleSheet.create({
}, },
servingsContainer: { servingsContainer: {
width: "100%", width: "100%",
marginBottom: 24, marginBottom: 14,
}, },
mealTypeContainer: { mealTypeContainer: {
width: "100%", width: "100%",
marginBottom: 24, marginBottom: 6,
}, },
mealTypeRow: { mealTypeRow: {
flexDirection: "row", flexDirection: "row",
@ -615,13 +652,18 @@ const styles = StyleSheet.create({
color: theme.colors.gray500, color: theme.colors.gray500,
}, },
totalCalories: { totalCalories: {
width: "100%", display: "none",
},
footerSummary: {
flexDirection: "row", flexDirection: "row",
justifyContent: "space-between", justifyContent: "space-between",
alignItems: "center", alignItems: "center",
paddingTop: 24, paddingHorizontal: 20,
paddingTop: 10,
paddingBottom: 10,
borderTopWidth: 1, borderTopWidth: 1,
borderTopColor: theme.colors.gray200, borderTopColor: theme.colors.gray200,
backgroundColor: "#fff",
}, },
totalLabel: { totalLabel: {
fontSize: 16, fontSize: 16,
@ -635,17 +677,20 @@ const styles = StyleSheet.create({
}, },
buttonRow: { buttonRow: {
flexDirection: "row", flexDirection: "row",
padding: 20, paddingHorizontal: 20,
paddingTop: 8,
paddingBottom: 16,
gap: 12, gap: 12,
backgroundColor: "#fff",
}, },
rescanButton: { rejectButton: {
flex: 1, flex: 1,
paddingVertical: 16, paddingVertical: 16,
borderRadius: 20, borderRadius: 20,
backgroundColor: theme.colors.gray100, backgroundColor: theme.colors.gray100,
alignItems: "center", alignItems: "center",
}, },
rescanButtonText: { rejectButtonText: {
fontSize: 16, fontSize: 16,
fontWeight: "700", fontWeight: "700",
color: theme.colors.gray700, color: theme.colors.gray700,