track meal and add water implemented
This commit is contained in:
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',
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user