71 lines
29 KiB
TypeScript
71 lines
29 KiB
TypeScript
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||
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 };
|
||
type ProfileData = { name:string; goal:string; journey:string; glucoseUnit:'mg/dL'|'mmol/L'; reminders:boolean; complete:boolean };
|
||
const C = { ink:'#15211c', muted:'#637169', cream:'#f6f7f2', white:'#fff', green:'#1d6b50', mint:'#dcefe5', coral:'#ef835f', yellow:'#f4d58d', line:'#e5e9e3' };
|
||
const seed: Entry[] = [
|
||
{date:'Lun',weight:91.4,water:1500,steps:6100,glucose:112},{date:'Mar',weight:91.2,water:1800,steps:7400,glucose:108},
|
||
{date:'Mer',weight:91.1,water:1600,steps:6900,glucose:110},{date:'Jeu',weight:90.9,water:2000,steps:8200,glucose:105},
|
||
{date:'Ven',weight:90.8,water:2100,steps:8600,glucose:104},{date:'Sam',weight:90.7,water:1750,steps:7200,glucose:107},{date:'Auj.',weight:90.6,water:1250,steps:6240,glucose:103},
|
||
];
|
||
|
||
function Card({children, style}: any){return <View style={[s.card,style]}>{children}</View>}
|
||
function Pill({children, tone='green'}:any){return <View style={[s.pill,{backgroundColor:tone==='coral'?'#fff0eb':C.mint}]}><Text style={[s.pillText,{color:tone==='coral'?'#9b432a':C.green}]}>{children}</Text></View>}
|
||
function IconTile({icon:Icon,color=C.green,bg=C.mint,size=22}:any){return <View style={[s.iconTile,{backgroundColor:bg}]}><Icon size={size} color={color} strokeWidth={2.2}/></View>}
|
||
function Metric({icon,label,value,sub,onPress}:any){const content=<><IconTile icon={icon}/><Text style={s.metricLabel}>{label}</Text><Text style={s.metricValue}>{value}</Text><Text style={s.metricSub}>{sub}</Text></>;return onPress?<Pressable accessibilityRole="button" accessibilityLabel={`${label}, ${value}. Ajouter`} onPress={onPress} style={({pressed})=>[s.metric,pressed&&s.pressed]}>{content}</Pressable>:<View accessibilityLabel={`${label}, ${value}, ${sub}`} style={s.metric}>{content}</View>}
|
||
function ProgressBar({value,color=C.green}:any){return <View style={s.track}><View style={[s.fill,{width:`${Math.min(value,100)}%`,backgroundColor:color}]}/></View>}
|
||
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 <View accessibilityRole="tablist" style={s.nav}>{items.map(([id,Icon,label])=>{const active=tab===id;return <Pressable accessibilityLabel={label} accessibilityRole="tab" accessibilityState={{selected:active}} key={id} onPress={()=>setTab(id)} style={({pressed})=>[s.navItem,active&&s.navItemActive,pressed&&s.pressed]}><Icon size={22} color={active?C.green:'#829087'} strokeWidth={active?2.6:2}/><Text style={[s.navLabel,active&&s.active]}>{label}</Text></Pressable>})}</View>}
|
||
|
||
export default function App(){
|
||
const [tab,setTab]=useState<Tab>('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<ProfileData>({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(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(!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)||g<glucoseBounds[0]||g>glucoseBounds[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'?<Today name={profile.name} score={score} water={water} setWater={setWater} steps={steps} setSteps={setSteps} go={setTab}/>:tab==='journal'?<Journal weight={weight} setWeight={setWeight} glucose={glucose} setGlucose={setGlucose} glucoseUnit={profile.glucoseUnit} water={water} setWater={setWater} steps={steps} setSteps={setSteps} sleep={sleep} setSleep={setSleep} meals={meals} setMeals={setMeals} mood={mood} setMood={setMood} save={save} saved={saved}/>:tab==='progress'?<Progress entries={entries}/>:tab==='coach'?<Coach journey={profile.journey}/>:<Profile profile={profile} setProfile={setProfile} entries={entries} reset={reset}/>,[tab,score,water,steps,weight,glucose,sleep,meals,mood,saved,entries,profile]);
|
||
if(!loaded)return <SafeAreaView style={s.safe}/>;
|
||
if(!profile.complete)return <Onboarding profile={profile} setProfile={setProfile}/>;
|
||
return <SafeAreaView style={s.safe}><StatusBar style="dark"/><View style={[s.shell,{maxWidth:width>900?1120:680}]}>{content}<Nav tab={tab} setTab={setTab}/></View></SafeAreaView>
|
||
}
|
||
|
||
function Onboarding({profile,setProfile}:any){const [step,setStep]=useState(0);const journeys:[[string,LucideIcon]]|any=[['Prévention',Sprout],['Perte de poids',Scale],['Diabète type 2',HeartPulse],['Traitement GLP-1',Sparkles]];return <SafeAreaView style={s.safe}><StatusBar style="dark"/><ScrollView contentContainerStyle={s.onboard}><View style={s.logo}><Sprout size={28} color={C.white}/></View><Text style={s.eyebrow}>BIENVENUE SUR METABLOOM</Text><Text style={s.onboardTitle}>{step===0?'Prenons soin de toi, simplement.':'Quel parcours te ressemble ?'}</Text><Text style={s.lead}>{step===0?'Quelques informations suffisent pour personnaliser ton expérience. Tu gardes le contrôle de tes données.':'Tu pourras modifier ce choix à tout moment. MetaBloom ne pose aucun diagnostic.'}</Text>{step===0?<><Text style={s.inputLabel}>Comment souhaites-tu être appelé·e ?</Text><View style={s.inputRow}><TextInput accessibilityLabel="Prénom ou pseudonyme" placeholder="Prénom ou pseudonyme" value={profile.name} onChangeText={(name)=>setProfile({...profile,name})} style={s.input}/></View>{!profile.name.trim()&&<Text style={s.helper}>Ce nom permettra de personnaliser ton accueil.</Text>}<Text style={s.inputLabel}>Ton objectif principal</Text><View style={s.inputRow}><TextInput accessibilityLabel="Objectif principal" value={profile.goal} onChangeText={(goal)=>setProfile({...profile,goal})} style={s.input}/></View></>:<View style={{gap:10}}>{journeys.map(([j,Icon]:[string,LucideIcon])=><Pressable key={j} accessibilityRole="radio" accessibilityState={{selected:profile.journey===j}} onPress={()=>setProfile({...profile,journey:j})} style={({pressed})=>[s.choice,profile.journey===j&&s.choiceActive,pressed&&s.pressed]}><IconTile icon={Icon}/><View style={{flex:1}}><Text style={s.tipTitle}>{j}</Text><Text style={s.body}>Conseils éducatifs et habitudes adaptés</Text></View>{profile.journey===j?<Check size={22} color={C.green}/>:<View style={{width:22}}/>}</Pressable>)}</View>}<Card style={s.safety}><Text style={s.tipTag}>À SAVOIR</Text><Text style={s.body}>MetaBloom est un outil de bien-être. Il ne remplace pas un professionnel de santé et ne modifie jamais un traitement.</Text></Card><Pressable disabled={step===0&&!profile.name.trim()} onPress={()=>step===0?setStep(1):setProfile({...profile,complete:true})} style={({pressed})=>[s.primary,step===0&&!profile.name.trim()&&{opacity:.4},pressed&&s.pressed]}><Text style={s.primaryText}>{step===0?'Continuer':'Accepter et commencer'}</Text></Pressable>{step===1&&<Pressable onPress={()=>setStep(0)} style={s.secondary}><Text style={s.secondaryText}>Retour</Text></Pressable>}</ScrollView></SafeAreaView>}
|
||
|
||
function Header({eyebrow,title,right}:any){return <View style={s.header}><View style={s.headerCopy}><Text style={s.eyebrow}>{eyebrow}</Text><Text style={s.title}>{title}</Text></View>{right&&<View style={s.headerAction}>{right}</View>}</View>}
|
||
function Today({name,score,water,setWater,steps,setSteps,go}:any){const date=new Intl.DateTimeFormat('fr-FR',{weekday:'long',day:'numeric',month:'long'}).format(new Date()).toUpperCase();return <ScrollView contentContainerStyle={s.page} showsVerticalScrollIndicator={false}>
|
||
<Header eyebrow={date} title={`Bonjour ${name}`} right={<View style={s.avatar}><Text style={s.avatarText}>{name[0]?.toUpperCase()}</Text></View>}/>
|
||
<LinearGradient colors={['#1d6b50','#2d8063']} style={s.hero}><View style={{flex:1}}><Pill>Aujourd’hui</Pill><Text style={s.heroTitle}>Chaque petit pas compte.</Text><Text style={s.heroText}>Tu avances à ton rythme, sans pression. Encore une action douce pour compléter ta journée.</Text><Pressable onPress={()=>go('journal')} style={s.heroButton}><Text style={s.heroButtonText}>Ajouter une donnée →</Text></Pressable></View><View style={s.ring}><Text style={s.ringBig}>{score}</Text><Text style={s.ringSmall}>/ 100</Text></View></LinearGradient>
|
||
<View style={s.sectionHead}><Text style={s.h2}>Mes essentiels</Text><Text style={s.caption}>Objectifs ajustables</Text></View>
|
||
<View style={s.grid}><Metric icon={Droplets} label="Hydratation" value={`${water/1000} L`} sub="sur 2 L" onPress={()=>setWater((x:number)=>Math.min(x+250,2500))}/><Metric icon={Footprints} label="Mouvement" value={steps.toLocaleString('fr-FR')} sub="sur 8 000 pas" onPress={()=>setSteps((x:number)=>x+500)}/><Metric icon={Utensils} label="Repas équilibrés" value="2 / 3" sub="très bien"/><Metric icon={Moon} label="Sommeil" value="7 h 12" sub="objectif 8 h"/></View>
|
||
<Card style={s.tip}><IconTile icon={Lightbulb} bg={C.yellow}/><View style={{flex:1}}><Text style={s.tipTag}>PETIT CONSEIL DU JOUR</Text><Text style={s.tipTitle}>Une marche de 10 minutes après le repas ?</Text><Text style={s.body}>Un peu de mouvement aide à se sentir mieux. Adapte toujours l’effort à ta situation.</Text></View></Card>
|
||
<Text style={s.disclaimer}>MetaBloom accompagne vos habitudes. Il ne remplace ni un diagnostic, ni un professionnel de santé.</Text>
|
||
</ScrollView>}
|
||
|
||
function Journal({weight,setWeight,glucose,setGlucose,glucoseUnit,water,setWater,steps,setSteps,sleep,setSleep,meals,setMeals,mood,setMood,save,saved}:any){return <ScrollView contentContainerStyle={s.page} keyboardShouldPersistTaps="handled"><Header eyebrow="SUIVI COMPLET" title="Mon journal" right={<Pill>Hors ligne</Pill>}/><Text style={s.lead}>Le poids et le sommeil sont nécessaires pour enregistrer la journée. Le reste est facultatif.</Text>
|
||
<Card><Text style={s.h2}>Mesures du jour</Text><Text style={s.inputLabel}>Poids</Text><View style={s.inputRow}><TextInput accessibilityLabel="Poids en kilogrammes, requis" value={weight} onChangeText={setWeight} keyboardType="decimal-pad" style={s.input}/><Text style={s.unit}>kg</Text></View><Text style={s.inputLabel}>Glycémie <Text style={s.optional}>facultatif · saisie manuelle</Text></Text><View style={s.inputRow}><TextInput accessibilityLabel="Glycémie facultative" value={glucose} onChangeText={setGlucose} keyboardType="decimal-pad" style={s.input}/><Text style={s.unit}>{glucoseUnit}</Text></View><Text style={s.helper}>Ces valeurs servent uniquement à ton suivi personnel. Aucune interprétation médicale n’est fournie.</Text></Card>
|
||
<Card><Text style={s.h2}>Habitudes</Text><Stepper icon="💧" label="Eau" value={`${water} ml`} minus={()=>setWater(Math.max(0,water-250))} plus={()=>setWater(water+250)}/><Stepper icon="👟" label="Pas" value={steps.toLocaleString('fr-FR')} minus={()=>setSteps(Math.max(0,steps-500))} plus={()=>setSteps(steps+500)}/></Card>
|
||
<Card><Text style={s.h2}>Bien-être</Text><Text style={s.inputLabel}>Sommeil</Text><View style={s.inputRow}><TextInput accessibilityLabel="Durée de sommeil" value={sleep} onChangeText={setSleep} keyboardType="decimal-pad" style={s.input}/><Text style={s.unit}>heures</Text></View><Text style={s.inputLabel}>Repas équilibrés</Text><View style={s.segment}>{[0,1,2,3].map(n=><Pressable accessibilityLabel={`${n} repas équilibrés`} key={n} onPress={()=>setMeals(n)} style={[s.segmentItem,meals===n&&s.segmentActive]}><Text style={[s.segmentText,meals===n&&{color:C.white}]}>{n}</Text></Pressable>)}</View><Text style={s.inputLabel}>Humeur</Text><View style={s.segment}>{['😔','😐','🙂','😄'].map(x=><Pressable accessibilityLabel={`Humeur ${x}`} key={x} onPress={()=>setMood(x)} style={[s.segmentItem,mood===x&&s.segmentActive]}><Text style={{fontSize:21}}>{x}</Text></Pressable>)}</View></Card>
|
||
<Card style={s.scan}><IconTile icon={Camera} bg="#fff0d7" color="#9b6327"/><Text style={s.tipTag}>REPAS</Text><Text style={s.tipTitle}>Scanner ou photographier un repas</Text><Text style={s.body}>La confirmation des aliments restera toujours nécessaire. L’analyse automatique arrivera avec une base nutritionnelle validée.</Text></Card>
|
||
<Pressable accessibilityRole="button" accessibilityLabel={saved?'Journée enregistrée':'Enregistrer la journée'} onPress={save} style={({pressed})=>[s.primary,pressed&&s.pressed]}>{saved&&<Check size={20} color={C.white}/>}<Text style={s.primaryText}>{saved?'Journée enregistrée':'Enregistrer la journée'}</Text></Pressable>
|
||
</ScrollView>}
|
||
function Stepper({icon,label,value,minus,plus}:any){const Icon=icon==='💧'?Droplets:Footprints;return <View style={s.stepper}><IconTile icon={Icon}/><View style={{flex:1}}><Text style={s.metricLabel}>{label}</Text><Text style={s.stepValue}>{value}</Text></View><Pressable accessibilityRole="button" accessibilityLabel={`Diminuer ${label}`} onPress={minus} style={({pressed})=>[s.stepBtn,pressed&&s.pressed]}><Minus size={20} color={C.green}/></Pressable><Pressable accessibilityRole="button" accessibilityLabel={`Augmenter ${label}`} onPress={plus} style={({pressed})=>[s.stepBtn,pressed&&s.pressed]}><Plus size={20} color={C.green}/></Pressable></View>}
|
||
|
||
function Progress({entries}:any){const recent:Entry[]=entries.slice(-7);if(recent.length===0)return <ScrollView contentContainerStyle={s.page}><Header eyebrow="MES TENDANCES" title="Mes progrès"/><Card style={s.empty}><IconTile icon={TrendingUp}/><Text style={s.tipTitle}>Tes tendances apparaîtront ici</Text><Text style={s.body}>Ajoute au moins deux journées pour suivre ton évolution, sans pression.</Text></Card></ScrollView>;const weights=recent.map(e=>e.weight).filter(Number.isFinite);const min=Math.min(...weights);const max=Math.max(...weights);const avg=(key:'water'|'steps')=>Math.round(recent.reduce((a,e)=>a+(Number(e[key])||0),0)/recent.length);const rawDelta=recent.length>1?recent.at(-1)!.weight-recent[0].weight:0;return <ScrollView contentContainerStyle={s.page}><Header eyebrow="7 DERNIERS JOURS" title="Mes progrès" right={<Pill tone="coral">{rawDelta<=0?'Baisse':'Hausse'} {Math.abs(rawDelta).toFixed(1).replace('.',',')} kg</Pill>}/><Text style={s.lead}>Observe les tendances, pas la perfection.</Text><Card><View style={s.sectionHead}><View style={{flex:1}}><Text style={s.metricLabel}>Tendance de poids</Text><Text style={s.bigNumber}>{recent.at(-1)!.weight} <Text style={s.bigUnit}>kg</Text></Text></View><Pill>Données personnelles</Pill></View><View style={s.chart}>{recent.map((e,i)=><View key={`${e.date}-${i}`} style={s.barCol}><Text style={s.barValue}>{e.weight}</Text><View style={[s.bar,{height:58+((max-e.weight)/(max-min||1))*55}]}/><Text style={s.barLabel}>{e.date}</Text></View>)}</View></Card>
|
||
<View style={s.grid}><Card style={s.stat}><IconTile icon={Droplets}/><Text style={s.bigNumber}>{(avg('water')/1000).toFixed(1).replace('.',',')} L</Text><Text style={s.body}>moyenne d’eau</Text></Card><Card style={s.stat}><IconTile icon={Footprints}/><Text style={s.bigNumber}>{avg('steps').toLocaleString('fr-FR')}</Text><Text style={s.body}>pas en moyenne</Text></Card></View>
|
||
<Card style={s.insight}><Text style={s.tipTag}>À RETENIR</Text><Text style={s.tipTitle}>Une semaine régulière 🌱</Text><Text style={s.body}>Tu as bougé au moins 6 000 pas chaque jour. Cette régularité compte davantage qu’une journée parfaite.</Text></Card></ScrollView>}
|
||
|
||
function Coach({journey}:any){const lessons:[[LucideIcon,string,string]]|any=[[Utensils,'Composer une assiette rassasiante','4 min'],[Footprints,'Bouger après un repas, à ton rythme','3 min'],[Lightbulb,'Sortir du tout ou rien','5 min']];return <ScrollView contentContainerStyle={s.page}><Header eyebrow="PARCOURS PERSONNALISÉ" title="Mon coach" right={<Pill>{journey}</Pill>}/><LinearGradient colors={['#563c82','#7659a6']} style={s.coachHero}><Sparkles size={24} color={C.yellow}/><Text style={s.heroTitle}>Ta semaine, sans pression.</Text><Text style={s.heroText}>3 petites étapes pédagogiques. Aucun conseil ne modifie ton traitement.</Text><ProgressBar value={42} color={C.yellow}/><Text style={s.coachProgress}>42 % du parcours hebdomadaire</Text></LinearGradient><Text style={s.h2}>Micro-leçons</Text>{lessons.map(([Icon,title,duration]:[LucideIcon,string,string])=><Pressable key={title} accessibilityRole="button" accessibilityLabel={`${title}, ${duration}`} style={({pressed})=>[s.card,s.lesson,pressed&&s.pressed]}><IconTile icon={Icon}/><View style={{flex:1}}><Text style={s.tipTitle}>{title}</Text><Text style={s.body}>{duration} · contenu éducatif</Text></View><ChevronRight size={22} color={C.muted}/></Pressable>)}<Card style={s.insight}><Text style={s.tipTag}>COACHING DU JOUR</Text><Text style={s.tipTitle}>Choisis une action si petite qu’elle paraît facile.</Text><Text style={s.body}>Par exemple : préparer une bouteille d’eau ou marcher cinq minutes. Tu décides selon ton énergie et les recommandations de ton soignant.</Text></Card><Card><IconTile icon={Apple}/><Text style={s.tipTag}>CONNEXIONS SANTÉ</Text><Text style={s.tipTitle}>Apple Santé & Health Connect</Text><Text style={s.body}>Disponible prochainement après activation des autorisations natives.</Text></Card></ScrollView>}
|
||
|
||
function Profile({profile,setProfile,entries,reset}:any){const exportData=async()=>{const csv=['date,poids,eau_ml,pas,glycemie',...entries.map((e:Entry)=>[e.iso||e.date,e.weight,e.water,e.steps,e.glucose||''].join(','))].join('\n');await Share.share({title:'Export MetaBloom',message:csv})};return <ScrollView contentContainerStyle={s.page}><Header eyebrow="MON ESPACE" title="Profil & sécurité"/><Card style={s.profile}><View style={s.avatarLarge}><Text style={s.avatarLargeText}>{profile.name[0]?.toUpperCase()}</Text></View><View style={{flex:1}}><Text style={s.tipTitle}>{profile.name}</Text><Text style={s.body}>{profile.goal} · {profile.journey}</Text></View></Card><Text style={s.h2}>Préférences</Text><Card style={s.setting}><IconTile icon={Activity}/><View style={{flex:1}}><Text style={s.metricLabel}>Unité glycémie</Text><Text style={s.body}>{profile.glucoseUnit}</Text></View><Pressable accessibilityRole="button" accessibilityLabel="Changer l’unité de glycémie" onPress={()=>setProfile({...profile,glucoseUnit:profile.glucoseUnit==='mg/dL'?'mmol/L':'mg/dL'})} style={({pressed})=>[s.smallButton,pressed&&s.pressed]}><Text style={s.smallButtonText}>Changer</Text></Pressable></Card><Card style={s.setting}><IconTile icon={Bell}/><View style={{flex:1}}><Text style={s.metricLabel}>Rappels doux</Text><Text style={s.body}>{profile.reminders?'Activés à 19 h':'Désactivés'}</Text></View><Switch accessibilityLabel="Activer les rappels" value={profile.reminders} onValueChange={(reminders)=>setProfile({...profile,reminders})} trackColor={{true:C.green}}/></Card><Pressable accessibilityRole="button" accessibilityLabel="Exporter mes données CSV" onPress={exportData} style={({pressed})=>[s.secondaryCard,pressed&&s.pressed]}><Download size={20} color={C.green}/><Text style={s.secondaryCardText}>Exporter mes données CSV</Text></Pressable><Card style={s.safety}><IconTile icon={ShieldCheck}/><Text style={s.tipTag}>SÉCURITÉ & CONFIDENTIALITÉ</Text><Text style={s.tipTitle}>Tes données restent sous ton contrôle.</Text><Text style={s.body}>Tu peux les exporter ou les supprimer à tout moment. La synchronisation en ligne n’est pas activée dans cette version.</Text></Card><Pressable onPress={()=>Alert.alert('Effacer les données locales ?','Cette action supprime les données de cet appareil et réinitialise l’application.',[{text:'Annuler'},{text:'Effacer',style:'destructive',onPress:reset}])} style={s.danger}><Text style={s.dangerText}>Effacer les données de cet appareil</Text></Pressable><Text style={s.disclaimer}>En cas de malaise ou d’urgence, contactez immédiatement les services d’urgence. MetaBloom ne fournit pas de conseil thérapeutique.</Text></ScrollView>}
|
||
|
||
const s=StyleSheet.create({safe:{flex:1,backgroundColor:C.cream},shell:{flex:1,width:'100%',alignSelf:'center'},page:{padding:20,paddingTop:26,paddingBottom:120,gap:16},pressed:{opacity:.72,transform:[{scale:.98}]},header:{flexDirection:'row',justifyContent:'space-between',alignItems:'center',gap:12,marginBottom:4},headerCopy:{flex:1,minWidth:0},headerAction:{maxWidth:'44%'},eyebrow:{fontSize:11,fontWeight:'800',letterSpacing:1.4,color:C.green},title:{fontSize:28,fontWeight:'800',color:C.ink,letterSpacing:-.7,marginTop:4},avatar:{width:44,height:44,borderRadius:22,backgroundColor:C.yellow,alignItems:'center',justifyContent:'center'},avatarText:{fontWeight:'800',color:C.ink,fontSize:17},hero:{borderRadius:28,padding:22,minHeight:230,flexDirection:'row',alignItems:'center',gap:14,overflow:'hidden'},heroTitle:{fontSize:27,lineHeight:32,fontWeight:'800',color:C.white,marginTop:18,maxWidth:360},heroText:{fontSize:14,lineHeight:21,color:'#dcebe4',marginTop:8,maxWidth:400},heroButton:{backgroundColor:C.white,borderRadius:14,paddingVertical:14,paddingHorizontal:16,alignSelf:'flex-start',marginTop:18,minHeight:48,justifyContent:'center'},heroButtonText:{color:C.green,fontWeight:'800'},ring:{width:96,height:96,borderWidth:8,borderColor:C.yellow,borderRadius:50,alignItems:'center',justifyContent:'center',backgroundColor:'#ffffff13'},ringBig:{color:C.white,fontWeight:'900',fontSize:28},ringSmall:{color:'#dcebe4',fontSize:11},pill:{borderRadius:99,paddingHorizontal:11,paddingVertical:6,alignSelf:'flex-start'},pillText:{fontSize:11,fontWeight:'800'},sectionHead:{flexDirection:'row',justifyContent:'space-between',alignItems:'center'},h2:{fontSize:19,fontWeight:'800',color:C.ink},caption:{fontSize:12,color:C.muted},grid:{flexDirection:'row',flexWrap:'wrap',gap:12},metric:{backgroundColor:C.white,borderRadius:20,padding:17,width:'48%',minWidth:150,minHeight:154,borderWidth:1,borderColor:C.line},iconTile:{width:44,height:44,borderRadius:14,alignItems:'center',justifyContent:'center',marginBottom:12},metricLabel:{fontSize:12,fontWeight:'700',color:C.muted},metricValue:{fontSize:22,fontWeight:'900',color:C.ink,marginTop:3},metricSub:{fontSize:11,color:C.muted,marginTop:3},card:{backgroundColor:C.white,borderRadius:22,padding:19,borderWidth:1,borderColor:C.line},tip:{flexDirection:'row',gap:14,backgroundColor:'#fffaf0'},tipTag:{fontSize:10,fontWeight:'900',letterSpacing:1.1,color:C.green,marginBottom:6},tipTitle:{fontSize:17,fontWeight:'800',color:C.ink,marginBottom:5},body:{fontSize:13,lineHeight:19,color:C.muted},disclaimer:{fontSize:11,lineHeight:17,textAlign:'center',color:C.muted,paddingHorizontal:16},nav:{position:'absolute',bottom:0,left:0,right:0,height:86,backgroundColor:'#fffffffa',borderTopWidth:1,borderColor:C.line,flexDirection:'row',paddingTop:8,paddingHorizontal:6},navItem:{flex:1,minHeight:62,alignItems:'center',justifyContent:'center',gap:4,borderRadius:16},navItemActive:{backgroundColor:'#edf7f1'},navLabel:{fontSize:10,fontWeight:'700',color:'#829087'},active:{color:C.green},lead:{fontSize:15,lineHeight:22,color:C.muted,marginTop:-8},inputLabel:{fontSize:13,fontWeight:'800',color:C.ink,marginTop:17,marginBottom:7},optional:{fontWeight:'500',color:C.muted},inputRow:{minHeight:56,borderWidth:1.5,borderColor:'#d8ded8',borderRadius:14,flexDirection:'row',alignItems:'center',paddingHorizontal:15,backgroundColor:C.white},input:{flex:1,fontSize:18,fontWeight:'800',color:C.ink,outlineStyle:'none'} as any,unit:{fontWeight:'700',color:C.muted},helper:{fontSize:11,lineHeight:16,color:C.muted,marginTop:12},stepper:{flexDirection:'row',alignItems:'center',gap:12,paddingVertical:13,borderBottomWidth:1,borderBottomColor:C.line},stepValue:{fontSize:17,fontWeight:'800',color:C.ink,marginTop:2},stepBtn:{width:48,height:48,borderRadius:14,backgroundColor:C.mint,alignItems:'center',justifyContent:'center'},stepBtnText:{fontSize:24,color:C.green,fontWeight:'700'},primary:{backgroundColor:C.green,borderRadius:16,minHeight:54,padding:16,alignItems:'center',justifyContent:'center',flexDirection:'row',gap:8},primaryText:{color:C.white,fontSize:15,fontWeight:'900'},track:{height:8,borderRadius:4,backgroundColor:'#ffffff3d',overflow:'hidden'},fill:{height:8,borderRadius:4},bigNumber:{fontSize:28,fontWeight:'900',color:C.ink,marginTop:4},bigUnit:{fontSize:14,color:C.muted},chart:{height:160,flexDirection:'row',alignItems:'flex-end',justifyContent:'space-between',marginTop:20},barCol:{flex:1,alignItems:'center'},bar:{width:22,backgroundColor:C.green,borderRadius:8,marginVertical:6},barValue:{fontSize:9,color:C.muted},barLabel:{fontSize:10,color:C.muted},stat:{width:'48%',minWidth:150},statIcon:{fontSize:22},empty:{alignItems:'center',paddingVertical:34},insight:{backgroundColor:C.mint},profile:{flexDirection:'row',alignItems:'center',gap:15},avatarLarge:{width:62,height:62,borderRadius:22,backgroundColor:C.yellow,alignItems:'center',justifyContent:'center'},avatarLargeText:{fontSize:23,fontWeight:'900',color:C.ink},setting:{flexDirection:'row',alignItems:'center',gap:13,padding:15},chevron:{fontSize:28,color:C.muted},safety:{backgroundColor:C.mint},danger:{minHeight:48,padding:16,alignItems:'center',justifyContent:'center'},dangerText:{color:'#a23d33',fontWeight:'800'},onboard:{padding:24,paddingTop:54,paddingBottom:50,gap:16,maxWidth:560,width:'100%',alignSelf:'center'},logo:{width:58,height:58,borderRadius:20,backgroundColor:C.green,alignItems:'center',justifyContent:'center',marginBottom:8},logoText:{color:C.white,fontSize:28,fontWeight:'900'},onboardTitle:{fontSize:34,lineHeight:40,fontWeight:'900',color:C.ink,letterSpacing:-1},choice:{backgroundColor:C.white,borderWidth:1,borderColor:C.line,borderRadius:18,padding:15,minHeight:76,flexDirection:'row',alignItems:'center',gap:12},choiceActive:{borderWidth:2,borderColor:C.green,backgroundColor:C.mint},choiceEmoji:{fontSize:24},check:{fontSize:18,fontWeight:'900',color:C.green},secondary:{minHeight:48,padding:14,alignItems:'center',justifyContent:'center'},secondaryText:{color:C.green,fontWeight:'800'},segment:{flexDirection:'row',gap:8},segmentItem:{height:48,flex:1,borderRadius:13,backgroundColor:C.cream,alignItems:'center',justifyContent:'center',borderWidth:1,borderColor:C.line},segmentActive:{backgroundColor:C.green,borderColor:C.green},segmentText:{fontWeight:'800',color:C.ink},scan:{backgroundColor:'#fff8ed',borderStyle:'dashed',borderColor:'#d8a960'},coachHero:{borderRadius:26,padding:22,gap:14},coachProgress:{fontSize:12,fontWeight:'700',color:'#eee8f7'},lesson:{flexDirection:'row',alignItems:'center',gap:12,minHeight:82},lessonIcon:{width:48,height:48,borderRadius:15,backgroundColor:C.cream,alignItems:'center',justifyContent:'center'},smallButton:{minHeight:44,paddingHorizontal:12,paddingVertical:9,borderRadius:11,backgroundColor:C.mint,justifyContent:'center'},smallButtonText:{color:C.green,fontWeight:'800',fontSize:12},secondaryCard:{backgroundColor:C.white,borderRadius:16,borderWidth:1,borderColor:C.green,minHeight:52,padding:16,alignItems:'center',justifyContent:'center',flexDirection:'row',gap:8},secondaryCardText:{color:C.green,fontWeight:'900'}});
|