feat: initial MetaBloom cross-platform MVP
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"enabledPlugins": {
|
||||
"expo@claude-plugins-official": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.expo/
|
||||
*.log
|
||||
.DS_Store
|
||||
@@ -0,0 +1,3 @@
|
||||
# Expo HAS CHANGED
|
||||
|
||||
Read the exact versioned docs at https://docs.expo.dev/versions/v57.0.0/ before writing any code.
|
||||
@@ -0,0 +1,56 @@
|
||||
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, StyleSheet, Text, TextInput, View, useWindowDimensions } from 'react-native';
|
||||
|
||||
type Tab = 'today' | 'journal' | 'progress' | 'profile';
|
||||
type Entry = { date: string; weight: number; water: number; steps: number; glucose?: number };
|
||||
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 Metric({emoji,label,value,sub,onPress}:any){return <Pressable onPress={onPress} style={({pressed})=>[s.metric,pressed&&{opacity:.7}]}><Text style={s.metricEmoji}>{emoji}</Text><Text style={s.metricLabel}>{label}</Text><Text style={s.metricValue}>{value}</Text><Text style={s.metricSub}>{sub}</Text></Pressable>}
|
||||
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:[Tab,string,string][]=[['today','⌂','Aujourd’hui'],['journal','+','Journal'],['progress','↗','Progrès'],['profile','○','Profil']];return <View style={s.nav}>{items.map(([id,icon,label])=><Pressable accessibilityRole="button" key={id} onPress={()=>setTab(id)} style={s.navItem}><Text style={[s.navIcon,tab===id&&s.active]}>{icon}</Text><Text style={[s.navLabel,tab===id&&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 [saved,setSaved]=useState(false); const {width}=useWindowDimensions();
|
||||
useEffect(()=>{AsyncStorage.getItem('metabloom').then(v=>{if(v){const d=JSON.parse(v);setEntries(d.entries||seed);setWater(d.water||1250);setSteps(d.steps||6240)}}).catch(()=>{})},[]);
|
||||
useEffect(()=>{AsyncStorage.setItem('metabloom',JSON.stringify({entries,water,steps})).catch(()=>{})},[entries,water,steps]);
|
||||
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); if(!w||w<30||w>300){Alert.alert('Valeur à vérifier','Saisis un poids compris entre 30 et 300 kg.');return} setEntries(x=>[...x.slice(0,6),{date:'Auj.',weight:w,water,steps,glucose:g||undefined}]);setSaved(true);setTimeout(()=>setSaved(false),2200)};
|
||||
const content=useMemo(()=>tab==='today'?<Today score={score} water={water} setWater={setWater} steps={steps} setSteps={setSteps} go={setTab}/>:tab==='journal'?<Journal weight={weight} setWeight={setWeight} glucose={glucose} setGlucose={setGlucose} water={water} setWater={setWater} steps={steps} setSteps={setSteps} save={save} saved={saved}/>:tab==='progress'?<Progress entries={entries}/>:<Profile reset={()=>{setEntries(seed);setWater(1250);setSteps(6240);AsyncStorage.removeItem('metabloom')}}/>,[tab,score,water,steps,weight,glucose,saved,entries]);
|
||||
return <SafeAreaView style={s.safe}><StatusBar style="dark"/><View style={[s.shell,{maxWidth:width>900?1120:640}]}>{content}<Nav tab={tab} setTab={setTab}/></View></SafeAreaView>
|
||||
}
|
||||
|
||||
function Header({eyebrow,title,right}:any){return <View style={s.header}><View><Text style={s.eyebrow}>{eyebrow}</Text><Text style={s.title}>{title}</Text></View>{right}</View>}
|
||||
function Today({score,water,setWater,steps,setSteps,go}:any){return <ScrollView contentContainerStyle={s.page} showsVerticalScrollIndicator={false}>
|
||||
<Header eyebrow="MARDI 21 JUILLET" title="Bonjour Vincent 👋" right={<View style={s.avatar}><Text style={s.avatarText}>V</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 emoji="💧" label="Hydratation" value={`${water/1000} L`} sub="sur 2 L" onPress={()=>setWater((x:number)=>Math.min(x+250,2500))}/><Metric emoji="👟" label="Mouvement" value={steps.toLocaleString('fr-FR')} sub="sur 8 000 pas" onPress={()=>setSteps((x:number)=>x+500)}/><Metric emoji="🥗" label="Repas équilibrés" value="2 / 3" sub="très bien"/><Metric emoji="🌙" label="Sommeil" value="7 h 12" sub="objectif 8 h"/></View>
|
||||
<Card style={s.tip}><View style={s.tipIcon}><Text style={{fontSize:22}}>☀️</Text></View><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,water,setWater,steps,setSteps,save,saved}:any){return <ScrollView contentContainerStyle={s.page} keyboardShouldPersistTaps="handled"><Header eyebrow="SUIVI SIMPLE" title="Mon journal" right={<Pill>🔒 Local</Pill>}/><Text style={s.lead}>Note ce qui compte pour toi. Rien n’est obligatoire.</Text>
|
||||
<Card><Text style={s.h2}>Mesures du jour</Text><Text style={s.inputLabel}>Poids <Text style={s.optional}>facultatif</Text></Text><View style={s.inputRow}><TextInput accessibilityLabel="Poids en kilogrammes" 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" value={glucose} onChangeText={setGlucose} keyboardType="decimal-pad" style={s.input}/><Text style={s.unit}>mg/dL</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>
|
||||
<Pressable onPress={save} style={({pressed})=>[s.primary,pressed&&{opacity:.8}]}><Text style={s.primaryText}>{saved?'✓ Journée enregistrée':'Enregistrer ma journée'}</Text></Pressable>
|
||||
</ScrollView>}
|
||||
function Stepper({icon,label,value,minus,plus}:any){return <View style={s.stepper}><Text style={{fontSize:24}}>{icon}</Text><View style={{flex:1}}><Text style={s.metricLabel}>{label}</Text><Text style={s.stepValue}>{value}</Text></View><Pressable onPress={minus} style={s.stepBtn}><Text style={s.stepBtnText}>−</Text></Pressable><Pressable onPress={plus} style={s.stepBtn}><Text style={s.stepBtnText}>+</Text></Pressable></View>}
|
||||
|
||||
function Progress({entries}:any){const min=Math.min(...entries.map((e:Entry)=>e.weight)); const max=Math.max(...entries.map((e:Entry)=>e.weight));return <ScrollView contentContainerStyle={s.page}><Header eyebrow="7 DERNIERS JOURS" title="Mes progrès" right={<Pill tone="coral">↘ 0,8 kg</Pill>}/><Text style={s.lead}>Observe les tendances, pas la perfection.</Text><Card><View style={s.sectionHead}><View><Text style={s.metricLabel}>Tendance de poids</Text><Text style={s.bigNumber}>{entries.at(-1).weight} <Text style={s.bigUnit}>kg</Text></Text></View><Pill>Progression douce</Pill></View><View style={s.chart}>{entries.map((e:Entry)=><View key={e.date} 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}><Text style={s.statIcon}>💧</Text><Text style={s.bigNumber}>1,7 L</Text><Text style={s.body}>moyenne d’eau</Text></Card><Card style={s.stat}><Text style={s.statIcon}>👟</Text><Text style={s.bigNumber}>7 436</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 Profile({reset}:any){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}>V</Text></View><View><Text style={s.tipTitle}>Vincent</Text><Text style={s.body}>Objectif : habitudes durables</Text></View></Card><Text style={s.h2}>Préférences</Text>{[['🎯','Objectif principal','Équilibre métabolique'],['⚖️','Unité de poids','Kilogrammes (kg)'],['🩸','Unité glycémie','mg/dL'],['🔔','Rappels','À configurer']].map(x=><Card key={x[1]} style={s.setting}><Text style={{fontSize:22}}>{x[0]}</Text><View style={{flex:1}}><Text style={s.metricLabel}>{x[1]}</Text><Text style={s.body}>{x[2]}</Text></View><Text style={s.chevron}>›</Text></Card>)}<Card style={s.safety}><Text style={s.tipTag}>SÉCURITÉ & CONFIDENTIALITÉ</Text><Text style={s.tipTitle}>Vos données restent sur cet appareil.</Text><Text style={s.body}>Ce prototype n’envoie aucune donnée vers un serveur. Avant un usage réel, une validation clinique, RGPD/HDS et réglementaire sera nécessaire.</Text></Card><Pressable onPress={()=>Alert.alert('Effacer les données locales ?','Cette action réinitialise la démonstration.',[{text:'Annuler'},{text:'Effacer',style:'destructive',onPress:reset}])} style={s.danger}><Text style={s.dangerText}>Effacer mes données locales</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},header:{flexDirection:'row',justifyContent:'space-between',alignItems:'center',marginBottom:4},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:13,paddingHorizontal:16,alignSelf:'flex-start',marginTop:18},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,borderWidth:1,borderColor:C.line},metricEmoji:{fontSize:24,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'},tipIcon:{width:46,height:46,borderRadius:14,backgroundColor:C.yellow,alignItems:'center',justifyContent:'center'},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:82,backgroundColor:'#fffffffa',borderTopWidth:1,borderColor:C.line,flexDirection:'row',paddingTop:9},navItem:{flex:1,alignItems:'center',justifyContent:'center',gap:3},navIcon:{fontSize:24,color:'#829087'},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:{height:56,borderWidth:1,borderColor:C.line,borderRadius:14,flexDirection:'row',alignItems:'center',paddingHorizontal:15,backgroundColor:C.cream},input:{flex:1,fontSize:20,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:42,height:42,borderRadius:13,backgroundColor:C.mint,alignItems:'center',justifyContent:'center'},stepBtnText:{fontSize:24,color:C.green,fontWeight:'700'},primary:{backgroundColor:C.green,borderRadius:16,padding:17,alignItems:'center'},primaryText:{color:C.white,fontSize:15,fontWeight:'900'},track:{height:8,borderRadius:4,backgroundColor:C.line,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},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:{padding:16,alignItems:'center'},dangerText:{color:'#a23d33',fontWeight:'800'}});
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present 650 Industries, Inc. (aka Expo)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,76 @@
|
||||
# MetaBloom
|
||||
|
||||
Compagnon métabolique moderne, francophone et sans culpabilisation pour aider les personnes concernées par le surpoids, le prédiabète ou le diabète de type 2 à suivre leurs habitudes quotidiennes.
|
||||
|
||||
> **Statut : prototype éducatif et bien-être.** MetaBloom ne diagnostique aucune maladie, ne calcule aucun traitement et ne remplace pas un professionnel de santé. En cas d’urgence, contactez les services d’urgence.
|
||||
|
||||
## Le produit
|
||||
|
||||
Une seule application mobile-first pour iOS, Android et le web :
|
||||
|
||||
- tableau de bord quotidien avec score souple et petites actions ;
|
||||
- journal rapide : poids, eau, pas et glycémie manuelle facultative ;
|
||||
- tendances sur 7 jours et encouragements non culpabilisants ;
|
||||
- préférences d’unités et informations de sécurité ;
|
||||
- persistance locale sur l’appareil, sans compte ni serveur pour ce MVP ;
|
||||
- interface responsive, accessible et utilisable au clavier.
|
||||
|
||||
## Pourquoi MetaBloom ?
|
||||
|
||||
Le marché combine trois familles : suivi nutritionnel (MyFitnessPal), coaching comportemental (Noom/WW) et journal du diabète (mySugr). MetaBloom se différencie comme **compagnon métabolique inclusif et francophone**, simple, calme, centré sur la prévention et la confidentialité. Il évite les objectifs extrêmes et la gamification punitive.
|
||||
|
||||
## Stack
|
||||
|
||||
- Expo SDK 57
|
||||
- React Native 0.86 + React 19
|
||||
- TypeScript
|
||||
- AsyncStorage pour les données locales
|
||||
- Expo Linear Gradient
|
||||
|
||||
## Démarrer
|
||||
|
||||
Prérequis : Node.js 20+ et npm.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run web
|
||||
```
|
||||
|
||||
Autres cibles :
|
||||
|
||||
```bash
|
||||
npm run android # appareil/émulateur Android ou Expo Go
|
||||
npm run ios # macOS + simulateur iOS, ou Expo Go
|
||||
npm run typecheck
|
||||
npm run build:web
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```text
|
||||
App.tsx Interface, navigation et état du MVP
|
||||
index.ts Point d’entrée Expo
|
||||
app.json Configuration multiplateforme
|
||||
assets/ Icônes et splash screen
|
||||
```
|
||||
|
||||
Le MVP reste volontairement monolithique pour accélérer la validation produit. Une V2 séparera écrans, composants, domaine et accès aux données.
|
||||
|
||||
## Données et sécurité
|
||||
|
||||
Les données de démonstration et saisies sont stockées exclusivement via AsyncStorage sur l’appareil. Aucun backend, tracker publicitaire ou transfert de données n’est inclus.
|
||||
|
||||
Avant une mise en production réelle : analyse d’impact RGPD, consentement explicite, chiffrement, gestion de l’effacement/export, évaluation HDS, audit de sécurité, validation clinique et qualification au regard du règlement européen MDR. Les fonctions de diagnostic, prédiction d’hypoglycémie, dosage d’insuline ou adaptation thérapeutique sont explicitement hors périmètre.
|
||||
|
||||
## Roadmap
|
||||
|
||||
1. Tests utilisateurs et accessibilité WCAG.
|
||||
2. Authentification et synchronisation chiffrée auto-hébergeable.
|
||||
3. Export PDF/CSV partageable avec consentement.
|
||||
4. Rappels locaux et intégrations HealthKit / Health Connect.
|
||||
5. Base alimentaire francophone et scan, après validation qualité.
|
||||
6. Parcours professionnels de santé et conformité réglementaire.
|
||||
|
||||
## Licence
|
||||
|
||||
MIT — voir `LICENSE`.
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "MetaBloom",
|
||||
"slug": "metabloom",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"userInterfaceStyle": "light",
|
||||
"description": "Des habitudes bienveillantes pour un meilleur équilibre métabolique.",
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"backgroundColor": "#E6F4FE",
|
||||
"foregroundImage": "./assets/android-icon-foreground.png",
|
||||
"backgroundImage": "./assets/android-icon-background.png",
|
||||
"monochromeImage": "./assets/android-icon-monochrome.png"
|
||||
},
|
||||
"predictiveBackGestureEnabled": false
|
||||
},
|
||||
"web": {
|
||||
"favicon": "./assets/favicon.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 384 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,8 @@
|
||||
import { registerRootComponent } from 'expo';
|
||||
|
||||
import App from './App';
|
||||
|
||||
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
|
||||
// It also ensures that whether you load the app in Expo Go or in a native build,
|
||||
// the environment is set up appropriately
|
||||
registerRootComponent(App);
|
||||
Generated
+6246
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "metabloom",
|
||||
"version": "1.0.0",
|
||||
"main": "index.ts",
|
||||
"dependencies": {
|
||||
"@react-native-async-storage/async-storage": "2.2.0",
|
||||
"expo": "~57.0.7",
|
||||
"expo-linear-gradient": "~57.0.1",
|
||||
"expo-status-bar": "~57.0.1",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"react-native": "0.86.0",
|
||||
"react-native-web": "^0.21.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "~19.2.2",
|
||||
"typescript": "~6.0.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"web": "expo start --web",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"build:web": "expo export --platform web"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "expo/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user