diff --git a/CLAUDE.md b/CLAUDE.md index dde3497..8290ce3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,63 +4,73 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -BitePlan is a mobile-first Vue 3 PWA for meal planning, raw↔cooked weight conversion, and shopping list management. It targets Android via Capacitor and is designed for a 480px-max viewport. All UI strings and documentation are in **Italian**. +BitePlan è un'app Android per meal planning, conversione crudo/cotto e lista della spesa. Il progetto è in **riscrittura completa da Vue 3 + Capacitor a Flutter**. Segui `sop.md` come piano di lavoro: ogni sezione del SOP corrisponde a una fase da completare in ordine. L'obiettivo finale è un APK Android buildabile via Docker. Tutta la UI e la UX sono in **italiano**. -## Commands +## Stato attuale + +Il codice Vue 3 esistente (`src/`, `tests/`, `vite.config.mjs`, ecc.) è il riferimento funzionale da portare in Flutter — non va esteso. Il nuovo codice Flutter va creato da zero seguendo `sop.md`. + +## Roadmap (da sop.md) + +1. Struttura progetto Flutter (`lib/`, `pubspec.yaml`, asset JSON) +2. Container dev: Docker + Xvfb + noVNC su porta 6080 (`docker/dev/`) +3. Container build headless APK (`docker/build/`) +4. `StorageService` — persistenza con `shared_preferences` +5. `ConversionService` — rawToCooked / cookedToRaw + asset `lib/data/conversions.json` +6. Modelli dati: `MealPlan`, `DayPlan`, `ShoppingItem` +7. Pagina Meal Planner (accordion per giorno, 3 pasti, add/remove voci) +8. Pagina Converter (ricerca alimento → selezione metodo → calcolo bidirezionale) +9. Pagina Shopping List (checklist, import da meal planner, svuota) +10. `BottomNavigationBar` (Pasti | Converti | Spesa), portrait lock, Material 3 + +## Comandi (quando il progetto Flutter esisterà) ```bash -npm run dev # Dev server at http://localhost:5173 -npm run build # Production build → dist/ -npm run preview # Preview built app +# Sviluppo via container noVNC +cd docker/dev && docker compose up +# → http://localhost:6080 (desktop GUI nel browser) +# Nel terminale noVNC: cd /workspace && flutter pub get && flutter run -npm test # Vitest unit + integration tests (watch mode) -npm run test:coverage # Coverage report (v8 provider) -npm run test:e2e # Playwright e2e (requires dev server running) -npm run test:e2e:ui # Playwright interactive UI mode - -# Single test file -npx vitest run tests/unit/conversion.test.js - -# Android APK -bash docker/build.sh # Debug APK -bash docker/build.sh --release # Signed release APK +# Build APK +bash docker/build/build.sh # debug +bash docker/build/build.sh --release # release firmato → dist/ ``` -E2E tests simulate iPhone 14 Pro viewport (393×852) with Italian locale. +## Architettura target (Flutter) -## Architecture - -**App.vue** is the root router — it conditionally renders three pages based on a `currentPage` ref (`meal`, `convert`, `shop`) and hosts the portrait-lock transform, `InfoPanel`, and `DocsPanel`. - -**Three pages** in `src/pages/`: -- `MealPlanner.vue` — 7-day plan (Mon–Sun), 3 meal slots each (colazione/pranzo/cena), per-day accordion via `MealCard.vue`, QR share, and "generate shopping list" export -- `Converter.vue` — real-time food search → select food+method → bidirectional raw↔cooked calculation using yield coefficients from `src/data/conversions.json` -- `ShoppingList.vue` — checklist with add/remove/check, importable from meal planner - -**State & persistence**: No Pinia/Vuex — pages use Vue composables + direct `localStorage` via the wrappers in `src/utils/storage.js` (`save(key, val)` / `load(key, default)`). - -**Conversion logic** lives entirely in `src/utils/conversion.js`: -```js -rawToCooked(food, method, rawGrams, db) = rawGrams * db[food][method].yield -cookedToRaw(food, method, cookedGrams, db) = cookedGrams / db[food][method].yield ``` -`yield = cooked_weight / raw_weight`. The database in `src/data/conversions.json` has 50+ foods × 1–4 cooking methods with coefficients sourced from CREA, SINU, USDA (see `docs/conversioni.md`). +lib/ +├── main.dart +├── app.dart # MaterialApp, routing, BottomNavigationBar +├── pages/ +│ ├── meal_planner_page.dart +│ ├── converter_page.dart +│ └── shopping_list_page.dart +├── widgets/ +│ ├── meal_card.dart +│ └── checkbox_item.dart +├── models/ +│ ├── meal_plan.dart +│ └── shopping_item.dart +├── services/ +│ ├── storage_service.dart +│ └── conversion_service.dart +└── data/ + └── conversions.json # 50+ alimenti × metodi cottura, yield = cotto/crudo +``` -**CSS**: Vanilla CSS only, no framework. Design tokens are CSS variables in `:root` in `src/style.css` (`--color-primary: #2d6a4f`, `--nav-height: 64px`, etc.). Buttons must be min 44px; layout is single-column. +**Stato**: Provider o Riverpod (nessuno store esterno pesante). +**Persistenza**: `shared_preferences`, chiavi `meals` e `shopping_list` (JSON serializzato). +**Conversione**: `rawToCooked(raw, yield) = raw * yield` / `cookedToRaw(cooked, yield) = cooked / yield`. +**UI**: Material 3, seed color `Color(0xFF2d6a4f)`, touch target minimo 48×48 dp. -## Testing +## Riferimento funzionale (Vue → Flutter) -- **Unit** (`tests/unit/`): Pure function tests for `conversion.js` and `storage.js` -- **Integration** (`tests/integration/`): Vue Test Utils component mounts with localStorage seeding -- **E2E** (`tests/e2e/`): Playwright against the running dev server - -`tests/setup.js` clears localStorage before/after each test. Vitest uses `happy-dom` environment. - -## Android Build - -The Docker pipeline in `docker/` handles the full Android build: -1. `npm run build` → `dist/` -2. `cap sync` → copies dist into Android project -3. Gradle builds the APK; release APK is signed with `docker/biteplan.jks` (gitignored) - -See `docker/README.md` for full APK build instructions. +| Vue (attuale) | Flutter (target) | +|---|---| +| `src/pages/MealPlanner.vue` | `lib/pages/meal_planner_page.dart` | +| `src/pages/Converter.vue` | `lib/pages/converter_page.dart` | +| `src/pages/ShoppingList.vue` | `lib/pages/shopping_list_page.dart` | +| `src/utils/storage.js` | `lib/services/storage_service.dart` | +| `src/utils/conversion.js` | `lib/services/conversion_service.dart` | +| `src/data/conversions.json` | `lib/data/conversions.json` (asset) | diff --git a/sop.md b/sop.md index e5fe188..e44340c 100644 --- a/sop.md +++ b/sop.md @@ -1,11 +1,11 @@ -# SOP — BitePlan (Vue 3 + Vite → Capacitor Android) +# SOP — BitePlan (Flutter → Android APK) ## Scope -App mobile-first con tre funzionalità: +App mobile Android con tre funzionalità: 1. **Meal Planner** — pianificazione settimanale (7 giorni × 3 pasti: colazione, pranzo, cena). Ogni pasto contiene una lista di voci testuali liberamente modificabili. -2. **Conversione crudo/cotto** — calcolo del peso cotto a partire dal peso crudo (e viceversa) tramite coefficienti di resa definiti in un JSON interno. +2. **Conversione crudo/cotto** — calcolo del peso cotto a partire dal peso crudo (e viceversa) tramite coefficienti di resa definiti in un asset JSON interno. 3. **Lista della spesa** — checklist con aggiunta, spunta e rimozione elementi. --- @@ -14,130 +14,158 @@ App mobile-first con tre funzionalità: | Livello | Scelta | |---|---| -| Frontend | Vue 3 + Vite | -| Stato | Composables (no store) | -| Persistenza | LocalStorage | -| UI | CSS base mobile-first | -| Mobile (fase successiva) | Capacitor Android | -| Build riproducibile (opzionale) | Docker | - -Sviluppo iniziale: `npm run dev` + Chrome DevTools modalità mobile. +| Framework | Flutter (Dart) | +| Stato | Provider o Riverpod | +| Persistenza | shared_preferences | +| UI | Material 3, mobile-first | +| Build APK | Docker (container build) | +| Sviluppo | Docker + noVNC (container dev con GUI) | --- ## Struttura progetto ``` -src/ +lib/ +├── main.dart +├── app.dart # MaterialApp, routing, BottomNavigationBar ├── pages/ -│ ├── MealPlanner.vue -│ ├── Converter.vue -│ └── ShoppingList.vue -├── components/ -│ ├── BottomNav.vue -│ ├── MealCard.vue -│ └── CheckboxItem.vue -├── data/ -│ └── conversions.json -├── utils/ -│ ├── conversion.js -│ └── storage.js -└── App.vue +│ ├── meal_planner_page.dart +│ ├── converter_page.dart +│ └── shopping_list_page.dart +├── widgets/ +│ ├── meal_card.dart # Accordion giorno +│ └── checkbox_item.dart +├── models/ +│ ├── meal_plan.dart +│ └── shopping_item.dart +├── services/ +│ ├── storage_service.dart # Wrapper shared_preferences +│ └── conversion_service.dart # rawToCooked / cookedToRaw +└── data/ + └── conversions.json # Asset: 50+ alimenti × metodi cottura + +docker/ +├── dev/ +│ ├── Dockerfile # Flutter SDK + Android SDK + Xvfb + noVNC +│ └── docker-compose.yml # Porta 6080 → noVNC, volume su ./ +├── build/ +│ ├── Dockerfile # Flutter SDK + Android SDK headless +│ └── build.sh # flutter build apk --release + firma +pubspec.yaml ``` --- -## Fase 1 — Setup +## Container dev (noVNC) + +Il container di sviluppo espone un desktop virtuale via browser, utile per eseguire l'emulatore Android o Android Studio senza installare nulla sull'host. + +**Stack interno**: Ubuntu + Flutter SDK + Android SDK + Xvfb + noVNC + websockify. ```bash -npm create vue@latest biteplan -cd biteplan -npm install -npm run dev +# Avvio +cd docker/dev +docker compose up + +# Accesso GUI +# → http://localhost:6080 (noVNC, nessuna password) ``` -Test mobile: Chrome → F12 → viewport 360×640. +Il volume mappa `./` (root del progetto) in `/workspace` nel container, quindi le modifiche ai file sono bidirezionali in tempo reale. + +**Workflow nel container**: +1. Aprire un terminale nel desktop noVNC +2. `cd /workspace && flutter pub get` +3. Avviare l'emulatore o collegare device via ADB +4. `flutter run` --- -## Fase 2 — Meal Planner +## Container build (APK) -**Struttura dati** +Build headless riproducibile, senza GUI. -```js -{ - lunedi: { colazione: [], pranzo: [], cena: [] }, - martedi: { ... } +```bash +# Debug +bash docker/build/build.sh + +# Release firmato +bash docker/build/build.sh --release +``` + +Pipeline interna: +1. `flutter pub get` +2. `flutter build apk --release` → `build/app/outputs/flutter-apk/app-release.apk` +3. Firma con `apksigner` (keystore montato come volume o secret) +4. `zipalign` + +L'APK firmato viene copiato in `dist/` nella root del progetto. + +--- + +## Modello dati + +**Meal Plan** +```dart +class MealPlan { + final Map days; // 'lunedi' … 'domenica' +} + +class DayPlan { + final List colazione; + final List pranzo; + final List cena; } ``` -**Funzionalità** -- Aggiungere/rimuovere voci per ogni pasto -- Visualizzare l'intera settimana -- Salvataggio automatico su LocalStorage +**Shopping Item** +```dart +class ShoppingItem { + final String id; + final String name; + bool checked; +} +``` --- -## Fase 3 — Conversione crudo/cotto - -**JSON** — `src/data/conversions.json` +## Conversione crudo/cotto +**Asset** — `lib/data/conversions.json` ```json { "pollo": { "forno": { "yield": 0.75 }, "padella": { "yield": 0.70 } }, - "riso": { "bollito": { "yield": 2.5 } } + "riso basmati": { "bollitura": { "yield": 3.00 } } } ``` `yield = peso_cotto / peso_crudo` -**Funzioni** — `src/utils/conversion.js` - -```js -export const rawToCooked = (food, method, raw, db) => raw * db[food][method].yield -export const cookedToRaw = (food, method, cooked, db) => cooked / db[food][method].yield +**Service** — `lib/services/conversion_service.dart` +```dart +double rawToCooked(double raw, double yield) => raw * yield; +double cookedToRaw(double cooked, double yield) => cooked / yield; ``` -**UX**: ricerca testuale sull'alimento → selezione alimento+metodo → input grammi → risultato in tempo reale. - -Esempio: 140 g pollo crudo → 105 g cotti al forno. +UX: ricerca testuale → selezione alimento + metodo → input grammi → risultato in tempo reale. --- -## Fase 4 — Lista della spesa +## Persistenza -**Struttura dati** - -```js -[{ id, name, checked }] -``` - -**Funzionalità**: aggiungi, spunta, elimina, svuota lista. +`StorageService` wrappa `shared_preferences`: +- Meal plan serializzato come JSON stringa sotto chiave `meals` +- Lista spesa serializzata come JSON stringa sotto chiave `shopping_list` --- -## Fase 5 — UI Mobile +## UI -Navigazione bottom bar: Pasti | Converti | Spesa. - -Linee guida: bottoni min 44px, input semplici, una funzione per schermata. - ---- - -## Fase 6 — Android (Capacitor) - -```bash -npm install @capacitor/core @capacitor/cli -npx cap init -npx cap add android -npm run build && npx cap sync && npx cap run android -``` - ---- - -## Fase 7 — Docker (opzionale) - -Pipeline: install → build frontend → cap sync → gradle build APK. +- `BottomNavigationBar` con 3 tab: Pasti | Converti | Spesa +- Material 3, `ColorScheme.fromSeed(seedColor: Color(0xFF2d6a4f))` +- Target touch minimo 48×48 dp (Material baseline) +- Orientamento bloccato in portrait (`SystemChrome.setPreferredOrientations`) --- @@ -145,16 +173,5 @@ Pipeline: install → build frontend → cap sync → gradle build APK. - Modifica coefficienti di conversione in-app - Calcolo kcal -- Generazione automatica lista spesa dai pasti pianificati +- Condivisione piano via QR code - Sync cloud - ---- - -## Checklist - -- [ ] Meal planner funzionante -- [ ] Conversioni corrette -- [ ] JSON alimenti presente (12+ voci) -- [ ] Lista spesa funzionante -- [ ] Persistenza attiva -- [ ] APK testabile