20 KiB
Teknisk Beskrivning - Flutter Frontend
Viktigt att komma ihåg vid implementering av nya funktioner och kodning är att inte använda Windows-sökvägar. Använd inte c:/dev/recipe-app/... eftersom bygg- och testmiljön är på en remote Ubuntu-server. Utveckling sker lokalt och test samt drift sker på remote server. Säkerställ att inga absoluta Windows-sökvägar används i koden, för att stödja bygg och drift på Linux/Ubuntu.
Senaste ändringar (2026-05-01)
Kvittoimport Fas 6b — komplett:
- Granskningssteg i
receipt_import_tab.dartmed per-rad checkbox, redigeringsdialog och destinationsväljare. - Destinationsväljare: Inventarie eller Baslager via
SegmentedButton<_Destination>i redigeringsdialogen. - Vid vald destination Baslager dolt antal/enhet-fält med informationstext.
- Inventarie-flöde: skapar ny post eller slår ihop (PATCH quantity) med befintlig post. Förhandsvisning av ny mängd visas i listan.
- Baslager-flöde: lägger till produkten om den inte redan finns. Befintliga baslagerprodukter visas med orange chip.
- Snackbar visar separat räkning: skapade/sammanslagna i inventarie + tillagda/hoppa-över i baslager.
_loadInventory()laddar nu inventarie och baslager i parallell viaFuture.wait.- AI-kategorisuggestion chip (grön) visas för premium-användare i granskningslistan.
Senaste ändringar (2026-04-25)
Arkitektur- och UX-förbättringar:
- Grid-vy för recept: Kolumnval (2/4/6/8) via ikon i AppShell, med Riverpod-provider och SharedPreferences.
- RecipesScreen är nu body-only, ingen egen Scaffold/AppBar.
- AppShell visar grid-ikon endast på /recipes.
- Buggfix: Produktväljaren i pantry/inventarie (ProductPickerField) — bottenark implementeras.
- Kodkvalitet: Inga absoluta Windows-sökvägar.
Tekniska förbättringar:
- Förbättrad bildhantering med normalisering av URL:er, fallback och diagnostikloggning.
- Robustare importflöde för recept med stöd för PDF, bild och ICA-länkar.
- User-scope för pantry och matplan: Alla baslager- och matplansdata är nu per användare.
Syfte och mål
- Isolerad Flutter-baserad frontend i separat Docker-service.
- Web först, men med arkitektur som kan återanvändas för Android/iOS.
- Stegvis migrering av funktioner från befintlig Next.js-frontend.
Arkitektur
Lager
- Presentation: Skärmar och widgets i
flutter/lib/features/*/presentation. - State/Application: Riverpod providers/notifiers i
flutter/lib/features/*/data. - Data/API:
ApiClientiflutter/lib/core/api. - Platform abstraction: Token storage interface i
flutter/lib/core/platform.
Routing
- GoRouter i flutter/lib/core/router/app_router.dart.
- Nuvarande routes:
/login— loginskärm/recipes— receptlista (ShellRoute med AppShell)/recipes/create— nytt recept, utanför ShellRoute/recipes/:id— receptdetalj, utanför ShellRoute/recipes/:id/edit— redigera recept, utanför ShellRoute/profile— profil (ShellRoute med AppShell)
/recipes/createmåste vara listad före/recipes/:idi routelistan för att undvika konflikt.- Detaljsidor (detalj, skapa, redigera) ligger utanför ShellRoute för att få full-screen med automatisk back-knapp.
Auth
- Login endpoint:
POST /api/auth/login.
Kända fallgropar och API-gotchas
Flutter Web och PDF MIME-typ
- Flutter Web skickar PDF-filer med MIME-typ
application/octet-streamistället förapplication/pdf. - Backend (
receipt-import.controller.ts) måste tillåta båda:application/pdfochapplication/octet-streamiALLOWED_MIMES. - Symptom om detta saknas: HTTP 400 "Otillåten filtyp. Använd JPEG, PNG, WebP eller PDF."
NestJS @Post() returnerar HTTP 201 som standard
- NestJS returnerar 201 (Created) för alla
@Post()-endpoints som standard. - Flutter-klienten (
ImportRepository) accepterar nu alla 2xx-svar (statusCode < 200 || statusCode >= 300). - Alternativt: lägg till
@HttpCode(200)på backend-controllern för att returnera 200. - Detta gjordes i
receipt-import.controller.ts(2026-04-30).
Kvittoimport — backend och arkitektur
/receipt-importhanteras av en dedikeradReceiptImportControllerochReceiptImportService, inte avQuickImportController.ReceiptImportServiceanvänder Mistral AI för att parsa kvitton till struktureradeParsedReceiptItem[]direkt — inget markdown.- Svaret från
/receipt-importär en array avParsedReceiptItem, inte{ markdown: "..." }. { markdown: "..." }är svaret från/quick-import(receptimport) — inte kvittoimport.
Övrigt
- Kvittoimport (Fas 6b): ✅ Klar (2026-05-01) — granskningssteg, destination-väljare, merge och spara till inventarie/baslager.
- Bildimport: Säkerställa att containrar är uppdaterade med senaste kod och att diagnostikloggar syns vid felsökning.
- Adminfunktioner: Avancerad AI-integration och ytterligare adminfunktioner planeras men är ej migrerade.
Syfte och mål
- Isolerad Flutter-baserad frontend i separat Docker-service.
- Web först, men med arkitektur som kan återanvändas för Android/iOS.
- Stegvis migrering av funktioner från befintlig Next.js-frontend.
Relaterade dokument
- next_steps_flutter.md
- README.md
- teknisk_beskrivning.md (för backend-kontext)
Arkitektur
Lager
- Presentation: Skärmar och widgets i
flutter/lib/features/*/presentation. - State/Application: Riverpod providers/notifiers i
flutter/lib/features/*/data. - Data/API:
ApiClientiflutter/lib/core/api. - Platform abstraction: Token storage interface i
flutter/lib/core/platform.
Routing
- GoRouter i flutter/lib/core/router/app_router.dart.
- Nuvarande routes:
/login— loginskärm/recipes— receptlista (ShellRoute med AppShell)/recipes/create— nytt recept, utanför ShellRoute/recipes/:id— receptdetalj, utanför ShellRoute/recipes/:id/edit— redigera recept, utanför ShellRoute/profile— profil (ShellRoute med AppShell)
/recipes/createmåste vara listad före/recipes/:idi routelistan för att undvika konflikt.- Detaljsidor (detalj, skapa, redigera) ligger utanför ShellRoute för att få full-screen med automatisk back-knapp.
Auth
- Login endpoint:
POST /api/auth/login. - Backend kontrakt använder
username+password. - Token-fält i svar:
accessToken. - Token lagras via
ITokenStorage(web implementation med SharedPreferences). - Auth-gate i router: utloggad användare redirectas till
/login, inloggad redirectas från/logintill/recipes. - Splash-skärm (
/) visas medan auth-state läses från storage vid app-start. guardedApiCall()iflutter/lib/core/api/guarded_api_call.darthanterar automatisk logout vid 401.
API-lager
ApiClientiflutter/lib/core/api/api_client.dartexponerar:getJson,postJson,patchJson,putJson,deleteJson.- Centraliserad HTTP-felklassning: 401 ->
ApiErrorType.unauthorized, 403 ->forbidden, 5xx ->server, nätverksfel ->network. ApiExceptioniflutter/lib/core/api/api_exception.dartär den enda feltypen som propageras från repositories.mapErrorToUserMessage(error, context)iflutter/lib/core/api/api_error_mapper.dartöversätter fel till lokaliserade användarmeddelanden. Tar numeraBuildContextsom andra argument för att hämta korrekt språk frånAppLocalizations.
Lokalisering
- Flutter
flutter_localizations+intltillagda ipubspec.yamlmedgenerate: true. - Konfigurationsfil: flutter/l10n.yaml med
synthetic-package: false. - Källsträngar i flutter/lib/l10n/app_sv.arb och flutter/lib/l10n/app_en.arb.
- Genererade filer hamnar i
flutter/lib/l10n/generated/och checkas inte in. - Hjälpare
context.l10ni flutter/lib/core/l10n/l10n.dart. MaterialApp.routerkonfigurerad medlocalizationsDelegates,supportedLocalesochlocale: const Locale('sv').- Dockerfilen kör
flutter gen-l10ninnanflutter build webför att generera Dart-koden i containerbygget. - Regressionstest i flutter/test/core/swedish_strings_regression_test.dart misslyckas om vanliga ASCII-varianter (Välj, Lägg, Försök, etc.) dyker upp igen i
lib/.
Användning av lokalisering
För att lägga till en ny lokaliserad sträng:
- Lägg till nyckeln i
app_sv.arb(ochapp_en.arb). - Kör
flutter gen-l10nlokalt (eller låt Docker-bygget göra det). - Använd
context.l10n.dinNyckeli widgetkoden.
För felsträngar från API: använd alltid mapErrorToUserMessage(error, context) — lägg inte in hårdkodade strängar.
Vad som är gjort
Infrastruktur
- Ny compose override: compose.flutter.yml.
- Ny Flutter-service:
recipe-flutter. - Service ansluten till nätverk:
recipe-internal(backend access)proxy(external Caddy reachability)
- Flutter-container serverar web build via intern Caddy.
- Exponering via extern Caddy med site
test.gynther.se->recipe-flutter:5000. - API-anrop går same-origin via
/apioch proxas internt tillrecipe-api:8080.
Inventarie (2026-04-22)
- Filtrering per plats (alla/kyl/frys/skåfferi) via
inventoryLocationFilterProvider. - Sortering (namn A-Ö, bäst före stigande/fallande) via
inventorySortFilterProvider. - Riverpod-query (
InventoryQuery) skickarlocationochsortsom queryparametrar till backenden. - Alla felmeddelanden går via
mapErrorToUserMessage(error, context).
Baslager/Pantry (2026-04-22)
- Pantry-produkter grupperas nu på kategori utifrån backend-relationen
categoryRef(rekursivparent-kedja ->categoryPath), med fallback till legacyproduct.categoryoch sistÖvrigt. PantryProductharcategoryIdochcategoryPathsom parsas från API-svaret.
Backend: User-scope för pantry och matplan (2026-04-22)
PantryItemochMealPlanEntryär nu user-scopade i Prisma-schemat.pantry-controller/service filtrerar alltid peruserIdfrån JWT.meal-plan-controller/service filtrerar alltid peruserId;inventoryCompareanvänder inloggad användares pantry.- Migration:
20260422130000_user_scope_pantry_meal_planapplicerad på server. - Next.js-frontenden krävde inga funktionella ändringar (förfrågningar går redan via auth-proxy).
Arkitektur
Lager
- Presentation: skarmar och widgets i
flutter/lib/features/*/presentation. - State/Application: Riverpod providers/notifiers i
flutter/lib/features/*/data. - Data/API:
ApiClientiflutter/lib/core/api. - Platform abstraction: token storage interface i
flutter/lib/core/platform.
Routing
- GoRouter i flutter/lib/core/router/app_router.dart.
- Nuvarande routes:
/login— loginskarm/recipes— receptlista (ShellRoute med AppShell)/recipes/create— nytt recept, utanfor ShellRoute/recipes/:id— receptdetalj, utanfor ShellRoute/recipes/:id/edit— redigera recept, utanfor ShellRoute/profile— profil (ShellRoute med AppShell)
/recipes/createmaste vara listad fore/recipes/:idi routelistan for att undvika konflikt.- Detaljsidor (detalj, skapa, redigera) ligger utanfor ShellRoute for att fa full-screen med automatisk back-knapp.
Auth
- Login endpoint:
POST /api/auth/login. - Backend kontrakt anvander
username+password. - Token-falt i svar:
accessToken. - Token lagras via
ITokenStorage(web implementation med SharedPreferences). - Auth-gate i router: utloggad anvandare redirectas till
/login, inloggad redirectas fran/logintill/recipes. - Splash-skarm (
/) visas medan auth-state lases fran storage vid app-start. guardedApiCall()iflutter/lib/core/api/guarded_api_call.darthanterar automatisk logout vid 401.
API-lager
ApiClientiflutter/lib/core/api/api_client.dartexponerar:getJson,postJson,patchJson,putJson,deleteJson.- Centralicerad HTTP-felklassning: 401 ->
ApiErrorType.unauthorized, 403 ->forbidden, 5xx ->server, natverksfel ->network. ApiExceptioniflutter/lib/core/api/api_exception.dartar den enda feltypen som propageras fran repositories.mapErrorToUserMessage(error, context)iflutter/lib/core/api/api_error_mapper.dartoversatter fel till lokaliserade anvandarmedddelanden. Tar numeraBuildContextsom andra argument for att hämta korrekt sprak franAppLocalizations.
Lokalisering (2026-04-22)
- Flutter
flutter_localizations+intltillagda ipubspec.yamlmedgenerate: true. - Konfigurationsfil: flutter/l10n.yaml med
synthetic-package: false. - Kallstrangar i flutter/lib/l10n/app_sv.arb och flutter/lib/l10n/app_en.arb.
- Generade filer hamnar i
flutter/lib/l10n/generated/och checkas inte in. - Hjalpare
context.l10ni flutter/lib/core/l10n/l10n.dart. MaterialApp.routerkonfigurerad medlocalizationsDelegates,supportedLocalesochlocale: const Locale('sv').- Dockerfilen kor
flutter gen-l10ninnanflutter build webfor att generera Dart-koden i containerbygget. - Regressionstest i flutter/test/core/swedish_strings_regression_test.dart failar om vanliga ASCII-varianter (Valj, Lagg, Forsok, etc.) dyker upp igen i
lib/.
Anvandning av lokalisering
For att lagga till en ny lokaliserad strang:
- Lagg till nyckeln i
app_sv.arb(ochapp_en.arb). - Kör
flutter gen-l10nlokalt (eller lat Docker-bygget gora det). - Anvand
context.l10n.dinNyckeli widgetkoden.
For felstrangar fran API: anvand alltid mapErrorToUserMessage(error, context) — lagg inte in hardkodade strängar.
Recipes
GET /api/recipes— receptlista.GET /api/recipes/:id— receptdetalj inkl. ingredienser.POST /api/recipes— skapa recept.PATCH /api/recipes/:id— uppdatera recept (OBS: PATCH, inte PUT).DELETE /api/recipes/:id— ta bort recept (returnerar 204, ingen body).POST /api/recipes/parse-markdown— tolka Markdown, returnerar ParsedRecipe med ingrediensforslagslistor.- Flutter Recipe-model:
name-fallback tilltitle, null-safe parsing av Decimal-strang till double.
Gemensamma UI-komponenter
LoadingStateView,EmptyStateView,ErrorStateViewiflutter/lib/core/ui/async_state_views.dart.AppShelliflutter/lib/core/ui/app_shell.dart: responsiv NavigationRail (bred) / NavigationBar (smal), delad AppBar med logout.
Kända API-fallgropar (viktigt!)
Regel: Kontrollera alltid HTTP-metod i [TEKNISK_BESKRIVNING.md i /recipe-app] innan en ny repository-metod implementeras.
| Operation | Korrekt metod | Fel som gjorts |
|---|---|---|
| Uppdatera recept | PATCH /api/recipes/:id |
PUT — ger 404 |
| Uppdatera inventariepost | PATCH /api/inventory/:id |
Använd PATCH |
| Uppdatera matplan | POST /api/meal-plan (upsert) |
Ingen PUT/PATCH |
Generell regel: NestJS-backenden använder PATCH för partiella uppdateringar, inte PUT. PUT exponeras inte på några resurser i detta projekt.
Drift och deploy
Build/run
- Build argument i compose:
API_BASE_URL=/api. - Build command:
docker compose -f compose.yml -f compose.flutter.yml build recipe-flutter
- Run command:
docker compose -f compose.yml -f compose.flutter.yml up -d --no-deps recipe-flutter
Rekommenderat kommandomönster
För att minska risken för fel startordning eller missförstånd mellan huvudappen och Flutter-spåret:
När huvudappen ska vara uppe:
docker compose up -d recipe-db recipe-api recipe-frontend
När Flutter-klienten ska vara uppe:
docker compose -f compose.yml -f compose.flutter.yml up -d --no-deps recipe-flutter
När både huvudappen och Flutter testas parallellt:
- Starta huvudappen med
compose.yml. - Starta sedan Flutter med override-filen
compose.flutter.yml.
Viktigt:
docker compose build ...bygger bara image.docker compose up -d ...krävs alltid för att containern faktiskt ska starta.
Viktiga verifieringar
- Compose merge valid:
docker compose -f compose.yml -f compose.flutter.yml config
- Container reachable from external Caddy:
docker exec caddy wget -S -O- http://recipe-flutter:5000
- Public endpoint:
curl -I https://test.gynther.se
Viktiga beslut
compose.ymllämnas orörd; Flutter ligger i separat override-fil.- Flutter-web använder same-origin API (
/api) för att undvika mixed-content. - Intern Caddy i Flutter-container hanterar static hosting +
/apiproxy. - Feature migration sker stegvis enligt next_steps_flutter.md.
Kända fallgropar
- Om
recipe-flutterinte är iproxynätverket blir det 502 från extern Caddy. - Om webbläsaren visar gammal JS kan gamla API-URL:er leva kvar i cache/service worker.
- Login med e-post fungerar inte om backend förväntar sig användarnamn.
recipe-flutterkan stoppas avdocker compose down --remove-orphansom kommandot körs utan override-filen och Flutter-spåret tidigare varit uppe.- En orphan-varning för
recipe-flutterär normalt förväntad när man kör huvudappen med baracompose.yml; det betyder inte att backend eller Prisma är trasiga.
Orphan-varning i praktiken
Om du ser en varning om orphan-containers under arbete med huvudappen betyder det oftast att recipe-flutter tidigare startats via:
docker compose -f compose.yml -f compose.flutter.yml up -d recipe-flutter
och att du nu kör ett kommando som bara använder compose.yml.
Detta är normalt och ofarligt så länge du vet vilken stack du avser att köra.
Om test.gynther.se slutar svara efter städning med --remove-orphans, starta om Flutter-spåret med:
docker compose -f compose.yml -f compose.flutter.yml up -d --no-deps recipe-flutter
Nyheter och förbättringar (2026-04-22)
- User-scope för pantry och matplan — Flutter-klienten använder nu user-scopade endpoints för baslager och matplan. JWT används för filtrering i alla anrop.
- Robust bildimport — Bild-URL normaliseras och skickas hela vägen till UI. Flutter hanterar både markdown och bild-url vid import.
- Importflöde — Quick-import och receipt-import har förbättrats med robust multipart-hantering, timeout, och felhantering. Prefill av markdown och bild-url fungerar i Flutter.
- Flutter-parity — Matplan, inventarie, baslager och receptflöden är nu fullt migrerade till Flutter med user-scope och robust felhantering.
- Felsökningslogg — Se
../IMPORT_IMAGE_DEBUG_2026-04-22.mdför detaljerad felsökningshistorik kring bildimport och importflöde.
Kända begränsningar
- Kvittoimport (Fas 6b) är påbörjad men granskningssteg och bulk-spara återstår.
- Bildimport kräver att containrar är uppdaterade med senaste kod — kontrollera att diagnostikloggar syns vid felsökning.
- Vissa adminfunktioner och avancerad AI-integration är planerade men ej migrerade.
Nyheter och tekniska förbättringar (2026-04-24)
- Navigationsflöden:
- Navigationslänkar har lagts till mellan recept, inventarie, baslager och matplan för att förbättra användarupplevelsen och minska antalet klick.
- Efter redigering av recept och konsumtion av inventariepost sker nu automatisk navigering till relevant vy.
- Efter import av recept sker automatisk navigering till receptlistan.
- UX och UI:
- Receptdetaljvyn har fått förbättrad bakgrundsbildshantering och mjukare scrollning.
- Plattformsoberoende kod:
- Genomgång av kodbasen för att säkerställa att inga absoluta Windows-sökvägar används, vilket gör projektet robust för Linux/Ubuntu-baserad bygg- och driftmiljö.
Se även README.md och next_steps_flutter.md för mer detaljer om användarflöden och leveransplan.