track meal and add water implemented

This commit is contained in:
dimitar committed 2025-12-01 19:31:07 +01:00
1 parent b0d751540e
commit ec370a3c17
169 files changed
+17981 -5

No files matched your search

@@ -0,0 +1,224 @@
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, Modal, TouchableOpacity, TextInput, KeyboardAvoidingView, Platform } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient';
import { theme } from '../styles/theme';
interface AddWaterModalProps {
visible: boolean;
onClose: () => void;
onAdd: (amount: number) => void;
onResetData?: () => void;
}
const QUICK_AMOUNTS = [
{ label: 'Small Glass', amount: 250, icon: 'water-outline' },
{ label: 'Large Glass', amount: 500, icon: 'water' },
{ label: 'Bottle', amount: 750, icon: 'flask' },
];
export function AddWaterModal({ visible, onClose, onAdd, onResetData }: AddWaterModalProps) {
const [customAmount, setCustomAmount] = useState('');
useEffect(() => {
if (visible) {
setCustomAmount('');
}
}, [visible]);
const handleReset = () => {
setCustomAmount('');
if (onResetData) {
onResetData();
}
};
const handleAdd = (amount: number) => {
onAdd(amount);
setCustomAmount('');
};
const handleCustomAdd = () => {
const amount = parseInt(customAmount, 10);
if (amount > 0) {
handleAdd(amount);
}
};
return (
<Modal
visible={visible}
transparent
animationType="slide"
onRequestClose={onClose}
>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.modalContainer}
>
<View style={styles.contentContainer}>
<View style={styles.header}>
<TouchableOpacity onPress={onClose} style={styles.closeButton}>
<Ionicons name="close" size={24} color={theme.colors.gray500} />
</TouchableOpacity>
<Text style={styles.title}>Add Water</Text>
<TouchableOpacity onPress={handleReset}>
<Text style={styles.resetText}>Reset</Text>
</TouchableOpacity>
</View>
<Text style={styles.subtitle}>Quick Add</Text>
<View style={styles.quickAddContainer}>
{QUICK_AMOUNTS.map((item) => (
<TouchableOpacity
key={item.amount}
onPress={() => handleAdd(item.amount)}
style={styles.quickAddItem}
>
<View style={styles.quickAddIcon}>
<Ionicons name={item.icon as any} size={24} color={theme.colors.primary} />
</View>
<Text style={styles.quickAddLabel}>{item.label}</Text>
<Text style={styles.quickAddAmount}>{item.amount}ml</Text>
</TouchableOpacity>
))}
</View>
<Text style={styles.subtitle}>Custom Amount</Text>
<View style={styles.customInputContainer}>
<TextInput
style={styles.input}
placeholder="e.g., 300"
value={customAmount}
onChangeText={setCustomAmount}
keyboardType="number-pad"
placeholderTextColor={theme.colors.gray400}
/>
<Text style={styles.unit}>ml</Text>
</View>
<TouchableOpacity onPress={handleCustomAdd} style={styles.addButtonContainer}>
<LinearGradient
colors={theme.gradients.ocean}
style={styles.addButton}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
>
<Text style={styles.addButtonText}>Add Water</Text>
</LinearGradient>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
</Modal>
);
}
const styles = StyleSheet.create({
modalContainer: {
flex: 1,
justifyContent: 'flex-end',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
contentContainer: {
backgroundColor: '#fff',
borderTopLeftRadius: 32,
borderTopRightRadius: 32,
padding: 24,
paddingBottom: 40,
...theme.shadows.strong,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 24,
},
title: {
fontSize: 24,
fontWeight: '700',
color: theme.colors.gray900,
},
closeButton: {
padding: 4,
},
resetText: {
fontSize: 16,
color: theme.colors.primary,
fontWeight: '600',
},
subtitle: {
fontSize: 16,
fontWeight: '600',
color: theme.colors.gray700,
marginBottom: 16,
},
quickAddContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 32,
gap: 12,
},
quickAddItem: {
flex: 1,
alignItems: 'center',
padding: 16,
backgroundColor: theme.colors.gray50,
borderRadius: 20,
borderWidth: 1,
borderColor: theme.colors.gray200,
},
quickAddIcon: {
width: 48,
height: 48,
borderRadius: 24,
backgroundColor: '#e0f2fe',
justifyContent: 'center',
alignItems: 'center',
marginBottom: 8,
},
quickAddLabel: {
fontSize: 12,
fontWeight: '600',
color: theme.colors.gray800,
marginBottom: 2,
textAlign: 'center',
},
quickAddAmount: {
fontSize: 12,
color: theme.colors.gray500,
},
customInputContainer: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 32,
},
input: {
flex: 1,
backgroundColor: theme.colors.gray50,
borderWidth: 1,
borderColor: theme.colors.gray200,
borderRadius: 16,
padding: 16,
fontSize: 16,
color: theme.colors.gray900,
marginRight: 12,
},
unit: {
fontSize: 16,
fontWeight: '600',
color: theme.colors.gray500,
},
addButtonContainer: {
marginTop: 8,
},
addButton: {
paddingVertical: 16,
borderRadius: 20,
alignItems: 'center',
},
addButtonText: {
color: '#fff',
fontSize: 18,
fontWeight: '700',
},
});
@@ -0,0 +1,120 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { LinearGradient } from 'expo-linear-gradient';
import { Ionicons } from '@expo/vector-icons';
import { theme } from '../styles/theme';
interface HydrationWidgetProps {
current: number; // in ml
goal: number; // in ml
}
export function HydrationWidget({ current, goal }: HydrationWidgetProps) {
const percentage = Math.min(Math.max(current / goal, 0), 1);
return (
<View style={styles.container}>
<LinearGradient
colors={['#e0f2fe', '#dbeafe']} // Light blue background
style={[styles.card, theme.shadows.subtle]}
>
<View style={styles.content}>
<View style={styles.iconContainer}>
<LinearGradient
colors={theme.gradients.ocean}
style={styles.icon}
>
<Ionicons name="water" size={24} color="#fff" />
</LinearGradient>
</View>
<View style={styles.info}>
<Text style={styles.title}>Hydration</Text>
<Text style={styles.subtitle}>{current} / {goal} ml</Text>
</View>
<View style={styles.percentageContainer}>
<Text style={styles.percentage}>{Math.round(percentage * 100)}%</Text>
</View>
</View>
<View style={styles.progressContainer}>
<View style={styles.progressBarBg}>
<LinearGradient
colors={theme.gradients.ocean}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
style={[styles.progressBarFill, { width: `${percentage * 100}%` }]}
/>
</View>
</View>
</LinearGradient>
</View>
);
}
const styles = StyleSheet.create({
container: {
marginHorizontal: 20,
marginBottom: 24,
},
card: {
borderRadius: 24,
padding: 20,
borderWidth: 1,
borderColor: '#bfdbfe',
},
content: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 16,
},
iconContainer: {
marginRight: 16,
},
icon: {
width: 48,
height: 48,
borderRadius: 16,
justifyContent: 'center',
alignItems: 'center',
},
info: {
flex: 1,
},
title: {
fontSize: 16,
fontWeight: '700',
color: theme.colors.gray900,
marginBottom: 4,
},
subtitle: {
fontSize: 14,
color: theme.colors.gray600,
fontWeight: '500',
},
percentageContainer: {
backgroundColor: '#fff',
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 12,
},
percentage: {
fontSize: 14,
fontWeight: '700',
color: theme.colors.primary,
},
progressContainer: {
height: 8,
},
progressBarBg: {
height: 8,
backgroundColor: 'rgba(255, 255, 255, 0.5)',
borderRadius: 4,
overflow: 'hidden',
},
progressBarFill: {
height: '100%',
borderRadius: 4,
},
});
@@ -36,7 +36,12 @@ function QuickActionItem({ icon, label, gradient, onPress }: QuickActionProps) {
);
}
export function QuickActionGrid() {
interface QuickActionGridProps {
onTrackMealPress?: () => void;
onAddWaterPress?: () => void;
}
export function QuickActionGrid({ onTrackMealPress, onAddWaterPress }: QuickActionGridProps) {
return (
<View style={styles.container}>
<Text style={styles.sectionTitle}>Quick Actions</Text>
@@ -50,11 +55,13 @@ export function QuickActionGrid() {
icon="restaurant"
label="Track Meal"
gradient={theme.gradients.success}
onPress={onTrackMealPress}
/>
<QuickActionItem
icon="water"
label="Add Water"
gradient={theme.gradients.ocean}
onPress={onAddWaterPress}
/>
<QuickActionItem
icon="scan"
@@ -0,0 +1,244 @@
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, Modal, TouchableOpacity, TextInput, KeyboardAvoidingView, Platform, ScrollView } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient';
import { theme } from '../styles/theme';
interface TrackMealModalProps {
visible: boolean;
onClose: () => void;
onSave: (meal: { type: string; name: string; calories: number }) => void;
onResetData?: () => void;
}
const MEAL_TYPES = ['Breakfast', 'Lunch', 'Dinner', 'Snack'];
export function TrackMealModal({ visible, onClose, onSave, onResetData }: TrackMealModalProps) {
const [selectedType, setSelectedType] = useState('Breakfast');
const [foodName, setFoodName] = useState('');
const [calories, setCalories] = useState('');
useEffect(() => {
if (visible) {
setFoodName('');
setCalories('');
setSelectedType('Breakfast');
}
}, [visible]);
const handleReset = () => {
setFoodName('');
setCalories('');
setSelectedType('Breakfast');
if (onResetData) {
onResetData();
}
};
const handleSave = () => {
if (!foodName || !calories) return;
onSave({
type: selectedType,
name: foodName,
calories: parseInt(calories, 10)
});
// Reset form
setFoodName('');
setCalories('');
setSelectedType('Breakfast');
};
return (
<Modal
visible={visible}
transparent
animationType="slide"
onRequestClose={onClose}
>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.modalContainer}
>
<View style={styles.contentContainer}>
<View style={styles.header}>
<TouchableOpacity onPress={onClose} style={styles.closeButton}>
<Ionicons name="close" size={24} color={theme.colors.gray500} />
</TouchableOpacity>
<Text style={styles.title}>Track Meal</Text>
<TouchableOpacity onPress={handleReset}>
<Text style={styles.resetText}>Reset</Text>
</TouchableOpacity>
</View>
<ScrollView showsVerticalScrollIndicator={false}>
{/* Meal Type Selection */}
<Text style={styles.label}>Meal Type</Text>
<View style={styles.typeContainer}>
{MEAL_TYPES.map((type) => (
<TouchableOpacity
key={type}
onPress={() => setSelectedType(type)}
style={[
styles.typeButton,
selectedType === type && styles.typeButtonActive
]}
>
<Text style={[
styles.typeText,
selectedType === type && styles.typeTextActive
]}>{type}</Text>
</TouchableOpacity>
))}
</View>
{/* Food Name Input */}
<Text style={styles.label}>Food Name</Text>
<TextInput
style={styles.input}
placeholder="e.g., Oatmeal with Berries"
value={foodName}
onChangeText={setFoodName}
placeholderTextColor={theme.colors.gray400}
/>
{/* Calories Input */}
<Text style={styles.label}>Calories</Text>
<View style={styles.caloriesInputContainer}>
<TextInput
style={[styles.input, styles.caloriesInput]}
placeholder="0"
value={calories}
onChangeText={setCalories}
keyboardType="number-pad"
placeholderTextColor={theme.colors.gray400}
/>
<Text style={styles.caloriesUnit}>kcal</Text>
</View>
{/* Save Button */}
<TouchableOpacity onPress={handleSave} style={styles.saveButtonContainer}>
<LinearGradient
colors={theme.gradients.primary}
style={styles.saveButton}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
>
<Text style={styles.saveButtonText}>Save Meal</Text>
</LinearGradient>
</TouchableOpacity>
</ScrollView>
</View>
</KeyboardAvoidingView>
</Modal>
);
}
const styles = StyleSheet.create({
modalContainer: {
flex: 1,
justifyContent: 'flex-end',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
contentContainer: {
backgroundColor: '#fff',
borderTopLeftRadius: 32,
borderTopRightRadius: 32,
padding: 24,
minHeight: '60%',
...theme.shadows.strong,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 24,
},
title: {
fontSize: 24,
fontWeight: '700',
color: theme.colors.gray900,
},
closeButton: {
padding: 4,
},
resetText: {
fontSize: 16,
color: theme.colors.primary,
fontWeight: '600',
},
label: {
fontSize: 16,
fontWeight: '600',
color: theme.colors.gray700,
marginBottom: 12,
marginTop: 8,
},
typeContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 8,
marginBottom: 24,
},
typeButton: {
paddingHorizontal: 16,
paddingVertical: 8,
borderRadius: 20,
backgroundColor: theme.colors.gray100,
borderWidth: 1,
borderColor: theme.colors.gray200,
},
typeButtonActive: {
backgroundColor: theme.colors.primaryLight,
borderColor: theme.colors.primary,
},
typeText: {
fontSize: 14,
fontWeight: '500',
color: theme.colors.gray600,
},
typeTextActive: {
color: '#fff',
fontWeight: '600',
},
input: {
backgroundColor: theme.colors.gray50,
borderWidth: 1,
borderColor: theme.colors.gray200,
borderRadius: 16,
padding: 16,
fontSize: 16,
color: theme.colors.gray900,
marginBottom: 24,
},
caloriesInputContainer: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 32,
},
caloriesInput: {
flex: 1,
marginBottom: 0,
marginRight: 12,
},
caloriesUnit: {
fontSize: 16,
fontWeight: '600',
color: theme.colors.gray500,
},
saveButtonContainer: {
marginTop: 'auto',
marginBottom: 24,
},
saveButton: {
paddingVertical: 16,
borderRadius: 20,
alignItems: 'center',
},
saveButtonText: {
color: '#fff',
fontSize: 18,
fontWeight: '700',
},
});