diff --git a/App.tsx b/App.tsx index ae06b50..9854baf 100644 --- a/App.tsx +++ b/App.tsx @@ -3,6 +3,9 @@ import { LinearGradient } from 'expo-linear-gradient'; import { StatusBar } from 'expo-status-bar'; import React, { useEffect, useMemo, useState } from 'react'; import { Alert, Pressable, SafeAreaView, ScrollView, Share, StyleSheet, Switch, Text, TextInput, View, useWindowDimensions } from 'react-native'; +import { Activity, Apple, Bell, Camera, Check, ChevronRight, CircleUserRound, Download, Droplets, Footprints, HeartPulse, Home, Lightbulb, Minus, Moon, Plus, Scale, ShieldCheck, Sparkles, Sprout, TrendingUp, Utensils } from 'lucide-react-native'; + +type LucideIcon = any; type Tab = 'today' | 'journal' | 'progress' | 'coach' | 'profile'; type Entry = { date: string; iso?: string; weight: number; water: number; steps: number; glucose?: number; sleep?: number; meals?: number; mood?: string }; @@ -16,17 +19,18 @@ const seed: Entry[] = [ function Card({children, style}: any){return {children}} function Pill({children, tone='green'}:any){return {children}} -function Metric({emoji,label,value,sub,onPress}:any){return [s.metric,pressed&&{opacity:.7}]}>{emoji}{label}{value}{sub}} +function IconTile({icon:Icon,color=C.green,bg=C.mint,size=22}:any){return } +function Metric({icon,label,value,sub,onPress}:any){const content=<>{label}{value}{sub};return onPress?[s.metric,pressed&&s.pressed]}>{content}:{content}} function ProgressBar({value,color=C.green}:any){return } -function Nav({tab,setTab}:any){const items:[Tab,string,string][]=[['today','⌂','Aujourd’hui'],['journal','+','Journal'],['progress','↗','Progrès'],['coach','✦','Coach'],['profile','○','Profil']];return {items.map(([id,icon,label])=>setTab(id)} style={s.navItem}>{icon}{label})}} +function Nav({tab,setTab}:any){const items:any[]=[['today',Home,'Aujourd’hui'],['journal',Plus,'Journal'],['progress',TrendingUp,'Progrès'],['coach',Sparkles,'Coach'],['profile',CircleUserRound,'Profil']];return {items.map(([id,Icon,label])=>{const active=tab===id;return setTab(id)} style={({pressed})=>[s.navItem,active&&s.navItemActive,pressed&&s.pressed]}>{label}})}} export default function App(){ const [tab,setTab]=useState('today'); const [entries,setEntries]=useState(seed); const [water,setWater]=useState(1250); const [steps,setSteps]=useState(6240); const [weight,setWeight]=useState('90.6'); const [glucose,setGlucose]=useState('103'); const [sleep,setSleep]=useState('7.2'); const [meals,setMeals]=useState(2); const [mood,setMood]=useState('🙂'); const [saved,setSaved]=useState(false); const [loaded,setLoaded]=useState(false); const [profile,setProfile]=useState({name:'',goal:'Retrouver mon équilibre',journey:'Prévention',glucoseUnit:'mg/dL',reminders:false,complete:false}); const {width}=useWindowDimensions(); - useEffect(()=>{AsyncStorage.getItem('metabloom-v2').then(v=>{if(v){const d=JSON.parse(v);setEntries(d.entries||seed);setWater(d.water||1250);setSteps(d.steps||6240);setProfile(d.profile||profile)}}).finally(()=>setLoaded(true))},[]); + useEffect(()=>{AsyncStorage.getItem('metabloom-v2').then(v=>{if(v){const d=JSON.parse(v);setEntries(Array.isArray(d.entries)&&d.entries.length?d.entries:seed);setWater(typeof d.water==='number'?d.water:1250);setSteps(typeof d.steps==='number'?d.steps:6240);setProfile(d.profile||profile)}}).catch(()=>{}).finally(()=>setLoaded(true))},[]); useEffect(()=>{if(loaded)AsyncStorage.setItem('metabloom-v2',JSON.stringify({entries,water,steps,profile})).catch(()=>{})},[entries,water,steps,profile,loaded]); const score=Math.round((Math.min(water/2000,1)+Math.min(steps/8000,1)+.82+.75)*25); - const save=()=>{const w=parseFloat(weight.replace(',','.')); const g=parseFloat(glucose.replace(',','.')); const sl=parseFloat(sleep.replace(',','.')); if(!w||w<30||w>300){Alert.alert('Valeur à vérifier','Saisis un poids compris entre 30 et 300 kg.');return} const glucoseBounds=profile.glucoseUnit==='mmol/L'?[1.1,33.3]:[20,600]; if(g && (gglucoseBounds[1])){Alert.alert('Valeur à vérifier','La glycémie saisie semble hors de la plage de saisie autorisée. Vérifie l’unité et la valeur.');return} if(!sl||sl>24){Alert.alert('Valeur à vérifier','Le sommeil doit être compris entre 0 et 24 heures.');return} const now=new Date();setEntries(x=>[...x.slice(-29),{date:'Auj.',iso:now.toISOString(),weight:w,water,steps,glucose:g||undefined,sleep:sl,meals,mood}]);setSaved(true);setTimeout(()=>setSaved(false),2200)}; + const save=()=>{const w=parseFloat(weight.replace(',','.')); const g=parseFloat(glucose.replace(',','.')); const sl=parseFloat(sleep.replace(',','.')); if(!Number.isFinite(w)||w<30||w>300){Alert.alert('Valeur à vérifier','Saisis un poids compris entre 30 et 300 kg.');return} const glucoseBounds=profile.glucoseUnit==='mmol/L'?[1.1,33.3]:[20,600]; if(glucose.trim()&&(!Number.isFinite(g)||gglucoseBounds[1])){Alert.alert('Valeur à vérifier','La glycémie saisie semble hors de la plage de saisie autorisée. Vérifie l’unité et la valeur.');return} if(!Number.isFinite(sl)||sl<0||sl>24){Alert.alert('Valeur à vérifier','Le sommeil doit être compris entre 0 et 24 heures.');return} const now=new Date();setEntries(x=>[...x.slice(-29),{date:'Auj.',iso:now.toISOString(),weight:w,water,steps,glucose:Number.isFinite(g)?g:undefined,sleep:sl,meals,mood}]);setSaved(true);setTimeout(()=>setSaved(false),2200)}; const reset=()=>{setEntries(seed);setWater(1250);setSteps(6240);setProfile({...profile,complete:false});AsyncStorage.removeItem('metabloom-v2')}; const content=useMemo(()=>tab==='today'?:tab==='journal'?:tab==='progress'?:tab==='coach'?:,[tab,score,water,steps,weight,glucose,sleep,meals,mood,saved,entries,profile]); if(!loaded)return ; @@ -34,33 +38,33 @@ export default function App(){ return 900?1120:680}]}>{content}