184 test, coverage ~93%; documenta il coverage helper e il vincolo sul caricamento async dei provider nei widget test. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6.8 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
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. Il rilascio avviene come APK Android buildato via Docker.
Ruolo atteso
Quando viene richiesta una modifica o una nuova funzionalità, segui questo flusso:
- Analizza prima di implementare: valuta se la richiesta ha senso — fattibilità, complessità reale, impatto su architettura/UX/persistenza. Se esiste un modo migliore di implementarla, proponilo; se la richiesta è mal posta o ha un costo sproporzionato al beneficio, dillo esplicitamente con l'alternativa. Motiva i giudizi con riferimenti concreti al codice.
- Implementa da esperto: se la richiesta va bene (o dopo l'eventuale discussione), implementala seguendo le convenzioni del progetto (feature-first, Provider, tutto in italiano).
- Suggerisci i test, non scriverli: al termine indica quali test coprirebbero la modifica (casi e file dove andrebbero), ma implementali solo se richiesto esplicitamente.
Comandi
# Sviluppo (host, no Docker)
emulator -avd biteplan & # AVD dedicato, Pixel 6 / Android 14 (google_apis x86_64)
flutter run # hot reload con "r", hot restart con "R"
# Analisi statica
flutter analyze # lint: package:flutter_lints/flutter.yaml
# Test rapidi (host)
flutter test # tutta la suite
flutter test test/features/meal_planner/qr_test.dart # singolo file
# Test riproducibili (Docker, immagine biteplan-build — usati come riferimento)
docker run --rm -v "$(pwd):/workspace" -w /workspace biteplan-build \
bash -c "flutter pub get --enforce-lockfile && flutter test"
# Integration test (richiede emulatore avviato)
flutter test integration_test/app_test.dart
# Build APK (headless, via Docker)
bash docker/build.sh # debug → dist/biteplan-debug.apk
export BITEPLAN_KEYSTORE_PASS=password # richiesto solo per --release
bash docker/build.sh --release # release → dist/biteplan-release.apk
Sviluppo locale
- Flutter host: installato in
~/flutter(tag3.41.9, stessa versione pinnata nel Dockerfile), nelPATHvia~/.bashrc. Docker non si usa più per il dev; resta per test di riferimento e build APK (isolamento,--enforce-lockfile, firma release). - Emulatore: Android SDK in
/home/davide/android-sdk; accelerazione hardware via/dev/kvm(WSL2 con KVM abilitato). - AVD
biteplan: dedicato a questo progetto (Pixel 6, Android 14, google_apis/x86_64). Non riutilizzare AVD di altri progetti (es.palladium_wallet). - Permessi: le build Docker girano come root nel volume montato — se
.dart_tool/obuild/risultano di proprietà root, servesudo chown -R davide:davide .dart_tool buildprima di usare flutter da host.
Build e firma
- Flutter pinnato: versione
3.41.9indocker/Dockerfile(ARG FLUTTER_VERSION); aggiornare anche.flutter-versione l'install host - Dipendenze:
pubspec.lockcommittato; la build usa--enforce-lockfilee fallisce se il lock non è allineato - Keystore:
docker/biteplan.jksè in.gitignore; da ottenere dall'autore o generare conkeytool. Password viaBITEPLAN_KEYSTORE_PASS(mai hardcoded); alias: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
Architettura
Feature-first sotto lib/features/ (meal_planner, converter, shopping_list, guide), ognuna con models/, providers/, presentation/. Codice trasversale in lib/shared/ e lib/core/.
lib/
├── app.dart # MaterialApp + NavigationBar; in initState chiama UpdateService.checkUpdate() e mostra showUpdateDialog()
├── core/constants/app_constants.dart # kDayIds, kMealSlots, kStorageKey*, kAppVersion
├── shared/
│ ├── services/storage_service.dart # wrapper SharedPreferences (load/save)
│ ├── services/update_service.dart # checkUpdate() → GitHub API; isNewer(), parseTagName() (@visibleForTesting)
│ ├── services/url_launcher_service.dart # MethodChannel 'com.davide.biteplan/launcher' → Intent.ACTION_VIEW
│ └── widgets/update_dialog.dart # showUpdateDialog() — "Più tardi" / "Scarica"
├── features/
│ ├── meal_planner/ # MealPlan/DayPlan, qr_codec.dart (buildQrPayload, parseMealPlanFromQr), pagine planner + scan QR
│ ├── converter/ # ConversionEntry con rawToCooked/cookedToRaw, dati da assets/data/conversions.json
│ ├── shopping_list/ # ShoppingItem con quantity per aggregazione duplicati
│ └── guide/ # guide_page (3 tab) + info_bottom_sheet
└── assets/data/conversions.json # 50+ alimenti × metodi cottura
State management: Provider (ChangeNotifier), uno per feature, registrati in main.dart con MultiProvider.
Persistenza: shared_preferences, chiavi meals e shopping_list (JSON serializzato).
Conversione: logica in ConversionEntry — rawToCooked = raw * yieldFactor, cookedToRaw = cooked / yieldFactor.
UI: Material 3, seed color Color(0xFF2d6a4f), tutto in italiano.
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.
Testing
184 test unit + widget in test/ (rispecchia la struttura di lib/), più un integration test in integration_test/app_test.dart. I test unit/widget non richiedono device. Coverage ~93% su lib/ (esclusi per scelta: main.dart, il percorso camera di qr_scan_page).
- Storage isolato con
SharedPreferences.setMockInitialValues({}) - Widget test: estensione
pumpAppintest/helpers/pump_app.dart - MethodChannel nei test:
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler— veditest/shared/widgets/update_dialog_test.dart test/coverage_helper_test.dartimporta tuttalib/così lcov include anche i file a coverage zero — aggiungere i nuovi file lì- Non chiamare
loadDb()/load()dei provider dentro il body ditestWidgets: l'async reale nella zona FakeAsync può bloccare la suite (timeout 10 min). Farlo nelsetUp— veditest/features/converter/pages/converter_page_test.dart