2026-05-16 16:38:23 +02:00
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
2026-05-17 23:39:00 +02:00
BitePlan è un'app Android per meal planning, conversione crudo/cotto e lista della spesa, scritta in Flutter. Tutta la UI e la UX sono in **italiano ** . L'obiettivo finale è un APK Android buildabile via Docker.
2026-05-16 16:38:23 +02:00
2026-05-16 17:41:45 +02:00
## Comandi
2026-05-16 16:38:23 +02:00
2026-05-16 16:43:26 +02:00
``` bash
2026-05-17 23:39:00 +02:00
# Sviluppo (web server con hot reload)
2026-05-16 16:43:26 +02:00
cd docker/dev && docker compose up
2026-05-17 23:39:00 +02:00
# → http://localhost:5173
# Con il container attivo: docker compose attach dev → r = hot reload
2026-05-16 17:41:45 +02:00
2026-05-17 23:39:00 +02:00
# Test (dalla root del progetto — richiede immagine biteplan-build)
docker run --rm -v " $( pwd ) :/workspace " -w /workspace biteplan-build \
2026-05-18 22:25:58 +02:00
bash -c "flutter pub get --enforce-lockfile && flutter test"
2026-05-16 17:41:45 +02:00
2026-05-17 23:39:00 +02:00
# Test singolo file
docker run --rm -v " $( pwd ) :/workspace " -w /workspace biteplan-build \
bash -c "flutter test test/features/meal_planner/qr_test.dart"
2026-05-16 16:43:26 +02:00
2026-05-17 23:39:00 +02:00
# Build APK (headless, da host)
2026-05-18 22:25:58 +02:00
bash docker/build/build.sh # debug → dist/biteplan-debug.apk
export BITEPLAN_KEYSTORE_PASS = password # richiesto solo per --release
bash docker/build/build.sh --release # release → dist/biteplan-release.apk
2026-05-16 16:38:23 +02:00
```
2026-05-18 22:25:58 +02:00
## Build e firma
- **Flutter pinnato**: versione `3.41.9` nei Dockerfile (`ARG FLUTTER_VERSION` ); aggiornare anche `.flutter-version`
- **Gradle cache**: named volume Docker `gradle-cache` , isolata dall'host
- **Dipendenze**: `pubspec.lock` committato; la build usa `--enforce-lockfile` e fallisce se il lock non è allineato
- **Keystore**: `docker/biteplan.jks` è in `.gitignore` ; da ottenere dall'autore o generare con `keytool`
- **Firma release**: password passata via `BITEPLAN_KEYSTORE_PASS` (mai hardcoded); alias keystore: `biteplan`
- **applicationId**: `com.davide.biteplan` — non cambiare, garantisce aggiornamento diretto dalla v1.2.1
- **versionCode**: letto da `pubspec.yaml` (`version: X.Y.Z+BUILD` ); BUILD deve essere sempre crescente
2026-05-17 23:39:00 +02:00
## Architettura
2026-05-16 16:38:23 +02:00
2026-05-16 16:43:26 +02:00
```
lib/
├── main.dart
2026-06-05 08:55:23 +02:00
├── app.dart # MaterialApp, NavigationBar; initState chiama UpdateService.checkUpdate() e mostra showUpdateDialog() se c'è aggiornamento
2026-05-17 23:39:00 +02:00
├── core/
│ ├── constants/app_constants.dart # kDayIds, kMealSlots, kStorageKey*, kAppVersion
│ └── theme/app_theme.dart
├── shared/
2026-06-05 08:55:23 +02:00
│ ├── services/
│ │ ├── storage_service.dart # wrapper SharedPreferences (load/save)
│ │ ├── update_service.dart # checkUpdate() → GitHub API, isNewer(), parseTagName() (@visibleForTesting)
│ │ └── url_launcher_service.dart # MethodChannel 'com.davide.biteplan/launcher' → Intent.ACTION_VIEW
2026-05-17 23:39:00 +02:00
│ └── widgets/
2026-06-05 08:55:23 +02:00
│ └── update_dialog.dart # showUpdateDialog() — AlertDialog con "Più tardi" / "Scarica"
2026-05-17 23:39:00 +02:00
├── features/
│ ├── meal_planner/
│ │ ├── models/meal_plan.dart # MealPlan, DayPlan
│ │ ├── providers/meal_planner_provider.dart
│ │ ├── qr_codec.dart # buildQrPayload, parseMealPlanFromQr
│ │ └── presentation/
│ │ ├── pages/meal_planner_page.dart
│ │ ├── pages/qr_scan_page.dart
│ │ └── widgets/meal_card.dart, qr_share_sheet.dart
│ ├── converter/
│ │ ├── models/conversion_entry.dart # rawToCooked, cookedToRaw (metodi sul model)
│ │ ├── providers/converter_provider.dart
│ │ └── presentation/pages/converter_page.dart
│ ├── shopping_list/
│ │ ├── models/shopping_item.dart # quantity per aggregazione duplicati
│ │ ├── providers/shopping_list_provider.dart
│ │ └── presentation/
│ │ ├── pages/shopping_list_page.dart
│ │ └── widgets/shopping_item_tile.dart
│ └── guide/
│ └── presentation/
│ ├── pages/guide_page.dart # 3 tab: Pasti, Converti, Spesa
│ └── widgets/info_bottom_sheet.dart # aperto dal bottone info in AppBar
└── assets/data/conversions.json # 50+ alimenti × metodi cottura
2026-05-16 16:43:26 +02:00
```
2026-05-16 16:38:23 +02:00
2026-05-17 23:39:00 +02:00
**State management ** : Provider (`ChangeNotifier` ).
2026-05-16 16:43:26 +02:00
**Persistenza ** : `shared_preferences` , chiavi `meals` e `shopping_list` (JSON serializzato).
2026-05-17 23:39:00 +02:00
**Conversione ** : logica in `ConversionEntry` — `rawToCooked = raw * yieldFactor` , `cookedToRaw = cooked / yieldFactor` .
**UI ** : Material 3, seed color `Color(0xFF2d6a4f)` , tutto in italiano.
2026-06-05 08:55:23 +02:00
**QR ** : payload JSON `{ "v": 1, "meals": { ... } }` , limite 2953 byte (capacità QR con error correction L).
**Update checker ** : `UpdateService.checkUpdate()` usa `dart:io` `HttpClient` (nessun package aggiuntivo); disabilitato su web (`kIsWeb` ). Il `MethodChannel` `com.davide.biteplan/launcher` è implementato in `android/app/src/main/kotlin/com/davide/biteplan/MainActivity.kt` .
**Widget test con MethodChannel ** : usare `TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler` per mockare il canale nei test — vedi `test/shared/widgets/update_dialog_test.dart` .
2026-05-16 16:43:26 +02:00
2026-05-16 17:41:45 +02:00
## Testing
2026-06-04 16:28:27 +02:00
130 test (unit + widget). Non richiedono device fisico.
2026-05-17 23:39:00 +02:00
2026-05-16 17:41:45 +02:00
```
test/
2026-05-17 23:39:00 +02:00
├── helpers/pump_app.dart # estensione pumpApp per widget test
2026-06-04 16:28:27 +02:00
├── features/
│ ├── converter/
│ │ ├── models/conversion_entry_test.dart
│ │ └── providers/converter_provider_test.dart
│ ├── meal_planner/
│ │ ├── models/meal_plan_test.dart
│ │ ├── providers/meal_planner_provider_test.dart
│ │ ├── widgets/meal_card_test.dart
│ │ └── qr_test.dart
│ └── shopping_list/
│ ├── models/shopping_item_test.dart
│ ├── providers/shopping_list_provider_test.dart
│ └── widgets/shopping_item_tile_test.dart
└── shared/
├── services/update_service_test.dart
└── widgets/update_dialog_test.dart
2026-05-16 17:41:45 +02:00
```
2026-05-16 16:43:26 +02:00
2026-05-17 23:39:00 +02:00
I test usano `SharedPreferences.setMockInitialValues({})` per isolare lo storage.