chore: aggiorna SOP e CLAUDE.md per migrazione a Flutter
Riscritto sop.md con stack Flutter + Docker (container dev noVNC + container build headless). Aggiornato CLAUDE.md con roadmap, architettura target e mapping Vue→Flutter come riferimento per la riscrittura. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,63 +4,73 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
|
|
||||||
## Project Overview
|
## 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
|
```bash
|
||||||
npm run dev # Dev server at http://localhost:5173
|
# Sviluppo via container noVNC
|
||||||
npm run build # Production build → dist/
|
cd docker/dev && docker compose up
|
||||||
npm run preview # Preview built app
|
# → 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)
|
# Build APK
|
||||||
npm run test:coverage # Coverage report (v8 provider)
|
bash docker/build/build.sh # debug
|
||||||
npm run test:e2e # Playwright e2e (requires dev server running)
|
bash docker/build/build.sh --release # release firmato → dist/
|
||||||
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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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`
|
| Vue (attuale) | Flutter (target) |
|
||||||
- **Integration** (`tests/integration/`): Vue Test Utils component mounts with localStorage seeding
|
|---|---|
|
||||||
- **E2E** (`tests/e2e/`): Playwright against the running dev server
|
| `src/pages/MealPlanner.vue` | `lib/pages/meal_planner_page.dart` |
|
||||||
|
| `src/pages/Converter.vue` | `lib/pages/converter_page.dart` |
|
||||||
`tests/setup.js` clears localStorage before/after each test. Vitest uses `happy-dom` environment.
|
| `src/pages/ShoppingList.vue` | `lib/pages/shopping_list_page.dart` |
|
||||||
|
| `src/utils/storage.js` | `lib/services/storage_service.dart` |
|
||||||
## Android Build
|
| `src/utils/conversion.js` | `lib/services/conversion_service.dart` |
|
||||||
|
| `src/data/conversions.json` | `lib/data/conversions.json` (asset) |
|
||||||
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.
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# SOP — BitePlan (Vue 3 + Vite → Capacitor Android)
|
# SOP — BitePlan (Flutter → Android APK)
|
||||||
|
|
||||||
## Scope
|
## 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.
|
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.
|
3. **Lista della spesa** — checklist con aggiunta, spunta e rimozione elementi.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -14,130 +14,158 @@ App mobile-first con tre funzionalità:
|
|||||||
|
|
||||||
| Livello | Scelta |
|
| Livello | Scelta |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Frontend | Vue 3 + Vite |
|
| Framework | Flutter (Dart) |
|
||||||
| Stato | Composables (no store) |
|
| Stato | Provider o Riverpod |
|
||||||
| Persistenza | LocalStorage |
|
| Persistenza | shared_preferences |
|
||||||
| UI | CSS base mobile-first |
|
| UI | Material 3, mobile-first |
|
||||||
| Mobile (fase successiva) | Capacitor Android |
|
| Build APK | Docker (container build) |
|
||||||
| Build riproducibile (opzionale) | Docker |
|
| Sviluppo | Docker + noVNC (container dev con GUI) |
|
||||||
|
|
||||||
Sviluppo iniziale: `npm run dev` + Chrome DevTools modalità mobile.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Struttura progetto
|
## Struttura progetto
|
||||||
|
|
||||||
```
|
```
|
||||||
src/
|
lib/
|
||||||
|
├── main.dart
|
||||||
|
├── app.dart # MaterialApp, routing, BottomNavigationBar
|
||||||
├── pages/
|
├── pages/
|
||||||
│ ├── MealPlanner.vue
|
│ ├── meal_planner_page.dart
|
||||||
│ ├── Converter.vue
|
│ ├── converter_page.dart
|
||||||
│ └── ShoppingList.vue
|
│ └── shopping_list_page.dart
|
||||||
├── components/
|
├── widgets/
|
||||||
│ ├── BottomNav.vue
|
│ ├── meal_card.dart # Accordion giorno
|
||||||
│ ├── MealCard.vue
|
│ └── checkbox_item.dart
|
||||||
│ └── CheckboxItem.vue
|
├── models/
|
||||||
├── data/
|
│ ├── meal_plan.dart
|
||||||
│ └── conversions.json
|
│ └── shopping_item.dart
|
||||||
├── utils/
|
├── services/
|
||||||
│ ├── conversion.js
|
│ ├── storage_service.dart # Wrapper shared_preferences
|
||||||
│ └── storage.js
|
│ └── conversion_service.dart # rawToCooked / cookedToRaw
|
||||||
└── App.vue
|
└── 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
|
```bash
|
||||||
npm create vue@latest biteplan
|
# Avvio
|
||||||
cd biteplan
|
cd docker/dev
|
||||||
npm install
|
docker compose up
|
||||||
npm run dev
|
|
||||||
|
# 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
|
```bash
|
||||||
{
|
# Debug
|
||||||
lunedi: { colazione: [], pranzo: [], cena: [] },
|
bash docker/build/build.sh
|
||||||
martedi: { ... }
|
|
||||||
|
# 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<String, DayPlan> days; // 'lunedi' … 'domenica'
|
||||||
|
}
|
||||||
|
|
||||||
|
class DayPlan {
|
||||||
|
final List<String> colazione;
|
||||||
|
final List<String> pranzo;
|
||||||
|
final List<String> cena;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Funzionalità**
|
**Shopping Item**
|
||||||
- Aggiungere/rimuovere voci per ogni pasto
|
```dart
|
||||||
- Visualizzare l'intera settimana
|
class ShoppingItem {
|
||||||
- Salvataggio automatico su LocalStorage
|
final String id;
|
||||||
|
final String name;
|
||||||
|
bool checked;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Fase 3 — Conversione crudo/cotto
|
## Conversione crudo/cotto
|
||||||
|
|
||||||
**JSON** — `src/data/conversions.json`
|
|
||||||
|
|
||||||
|
**Asset** — `lib/data/conversions.json`
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"pollo": { "forno": { "yield": 0.75 }, "padella": { "yield": 0.70 } },
|
"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`
|
`yield = peso_cotto / peso_crudo`
|
||||||
|
|
||||||
**Funzioni** — `src/utils/conversion.js`
|
**Service** — `lib/services/conversion_service.dart`
|
||||||
|
```dart
|
||||||
```js
|
double rawToCooked(double raw, double yield) => raw * yield;
|
||||||
export const rawToCooked = (food, method, raw, db) => raw * db[food][method].yield
|
double cookedToRaw(double cooked, double yield) => cooked / yield;
|
||||||
export const cookedToRaw = (food, method, cooked, db) => cooked / db[food][method].yield
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**UX**: ricerca testuale sull'alimento → selezione alimento+metodo → input grammi → risultato in tempo reale.
|
UX: ricerca testuale → selezione alimento + metodo → input grammi → risultato in tempo reale.
|
||||||
|
|
||||||
Esempio: 140 g pollo crudo → 105 g cotti al forno.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Fase 4 — Lista della spesa
|
## Persistenza
|
||||||
|
|
||||||
**Struttura dati**
|
`StorageService` wrappa `shared_preferences`:
|
||||||
|
- Meal plan serializzato come JSON stringa sotto chiave `meals`
|
||||||
```js
|
- Lista spesa serializzata come JSON stringa sotto chiave `shopping_list`
|
||||||
[{ id, name, checked }]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Funzionalità**: aggiungi, spunta, elimina, svuota lista.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Fase 5 — UI Mobile
|
## UI
|
||||||
|
|
||||||
Navigazione bottom bar: Pasti | Converti | Spesa.
|
- `BottomNavigationBar` con 3 tab: Pasti | Converti | Spesa
|
||||||
|
- Material 3, `ColorScheme.fromSeed(seedColor: Color(0xFF2d6a4f))`
|
||||||
Linee guida: bottoni min 44px, input semplici, una funzione per schermata.
|
- Target touch minimo 48×48 dp (Material baseline)
|
||||||
|
- Orientamento bloccato in portrait (`SystemChrome.setPreferredOrientations`)
|
||||||
---
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -145,16 +173,5 @@ Pipeline: install → build frontend → cap sync → gradle build APK.
|
|||||||
|
|
||||||
- Modifica coefficienti di conversione in-app
|
- Modifica coefficienti di conversione in-app
|
||||||
- Calcolo kcal
|
- Calcolo kcal
|
||||||
- Generazione automatica lista spesa dai pasti pianificati
|
- Condivisione piano via QR code
|
||||||
- Sync cloud
|
- Sync cloud
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Checklist
|
|
||||||
|
|
||||||
- [ ] Meal planner funzionante
|
|
||||||
- [ ] Conversioni corrette
|
|
||||||
- [ ] JSON alimenti presente (12+ voci)
|
|
||||||
- [ ] Lista spesa funzionante
|
|
||||||
- [ ] Persistenza attiva
|
|
||||||
- [ ] APK testabile
|
|
||||||
|
|||||||
Reference in New Issue
Block a user