# 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. Att inte använda c:/dev/recpie-app.... Detta eftersom bygg- och testmiljön är på en remote ubuntu-server. Utveckling sker lokalt och test samt drift sker på remote server. Detta dokument beskriver vad som ar byggt, arkitekturval och teknisk kontext for Flutter-frontenden. Malgrupp: utvecklare och AI-assistent i denna chat. Relaterade dokument: - [next_steps_flutter.md](next_steps_flutter.md) - [flutter/README.md](flutter/README.md) - [teknisk_beskrivning.md](teknisk_beskrivning.md) (for backend-kontekst) ## Syfte - Isolerad Flutter-baserad frontend i separat Docker-service. - Web forst, men med arkitektur som kan ateranvandas for Android/iOS. - Stegvis migrering av funktioner fran befintlig Next.js-frontend. ## Vad som ar gjort - Ny compose override: [compose.flutter.yml](compose.flutter.yml). - Ny Flutter-service: `recipe-flutter`. - Service ansluten till natverk: - `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 gar same-origin via `/api` och proxas internt till `recipe-api:8080`. ### Inventarie (2026-04-22) - Filtrering per plats (alla/kyl/frys/skafferi) via `inventoryLocationFilterProvider`. - Sortering (namn A-O, bast fore stigande/fallande) via `inventorySortFilterProvider`. - Riverpod-query (`InventoryQuery`) skickar `location` och `sort` som queryparametrar till backenden. - Alla felmeddelanden gar via `mapErrorToUserMessage(error, context)`. ### Baslager/Pantry (2026-04-22) - Pantry-produkter grupperas nu pa kategori utifrån backend-relationen `categoryRef` (rekursiv `parent`-kedja -> `categoryPath`), med fallback till legacy `product.category` och sist `Ovrigt`. - `PantryProduct` har `categoryId` och `categoryPath` som parsas fran API-svaret. ### Backend: user-scope for pantry och matplan (2026-04-22) - `PantryItem` och `MealPlanEntry` ar nu user-scopade i Prisma-schemat. - `pantry`-controller/service filtrerar alltid per `userId` fran JWT. - `meal-plan`-controller/service filtrerar alltid per `userId`; `inventoryCompare` anvander inloggad anvandares pantry. - Migration: `20260422130000_user_scope_pantry_meal_plan` applicerad pa server. - Next.js-frontenden kravde inga funktionella andringar (forfrågningar gar 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: `ApiClient` i `flutter/lib/core/api`. - Platform abstraction: token storage interface i `flutter/lib/core/platform`. ### Routing - GoRouter i [flutter/lib/core/router/app_router.dart](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/create` maste vara listad fore `/recipes/:id` i 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 `/login` till `/recipes`. - Splash-skarm (`/`) visas medan auth-state lases fran storage vid app-start. - `guardedApiCall()` i `flutter/lib/core/api/guarded_api_call.dart` hanterar automatisk logout vid 401. ### API-lager - `ApiClient` i `flutter/lib/core/api/api_client.dart` exponerar: `getJson`, `postJson`, `patchJson`, `putJson`, `deleteJson`. - Centralicerad HTTP-felklassning: 401 -> `ApiErrorType.unauthorized`, 403 -> `forbidden`, 5xx -> `server`, natverksfel -> `network`. - `ApiException` i `flutter/lib/core/api/api_exception.dart` ar den enda feltypen som propageras fran repositories. - `mapErrorToUserMessage(error, context)` i `flutter/lib/core/api/api_error_mapper.dart` oversatter fel till lokaliserade anvandarmedddelanden. Tar numera `BuildContext` som andra argument for att hämta korrekt sprak fran `AppLocalizations`. ### Lokalisering (2026-04-22) - Flutter `flutter_localizations` + `intl` tillagda i `pubspec.yaml` med `generate: true`. - Konfigurationsfil: [flutter/l10n.yaml](flutter/l10n.yaml) med `synthetic-package: false`. - Kallstrangar i [flutter/lib/l10n/app_sv.arb](flutter/lib/l10n/app_sv.arb) och [flutter/lib/l10n/app_en.arb](flutter/lib/l10n/app_en.arb). - Generade filer hamnar i `flutter/lib/l10n/generated/` och checkas inte in. - Hjalpare `context.l10n` i [flutter/lib/core/l10n/l10n.dart](flutter/lib/core/l10n/l10n.dart). - `MaterialApp.router` konfigurerad med `localizationsDelegates`, `supportedLocales` och `locale: const Locale('sv')`. - Dockerfilen kor `flutter gen-l10n` innan `flutter build web` for att generera Dart-koden i containerbygget. - Regressionstest i [flutter/test/core/swedish_strings_regression_test.dart](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: 1. Lagg till nyckeln i `app_sv.arb` (och `app_en.arb`). 2. Kör `flutter gen-l10n` lokalt (eller lat Docker-bygget gora det). 3. Anvand `context.l10n.dinNyckel` i 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 till `title`, null-safe parsing av Decimal-strang till double. ### Gemensamma UI-komponenter - `LoadingStateView`, `EmptyStateView`, `ErrorStateView` i `flutter/lib/core/ui/async_state_views.dart`. - `AppShell` i `flutter/lib/core/ui/app_shell.dart`: responsiv NavigationRail (bred) / NavigationBar (smal), delad AppBar med logout. ## Kanda 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` | Anvand PATCH | | Uppdatera matplan | `POST /api/meal-plan` (upsert) | Ingen PUT/PATCH | Generell regel: NestJS-backenden anvander `PATCH` for partiella uppdateringar, inte `PUT`. `PUT` exponeras inte pa nagra 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 kommandomonster For att minska risken for fel startordning eller missforstand mellan huvudappen och Flutter-sparet: **Nar huvudappen ska vara uppe:** - `docker compose up -d recipe-db recipe-api recipe-frontend` **Nar Flutter-klienten ska vara uppe:** - `docker compose -f compose.yml -f compose.flutter.yml up -d --no-deps recipe-flutter` **Nar bade huvudappen och Flutter testas parallellt:** 1. Starta huvudappen med `compose.yml`. 2. Starta sedan Flutter med override-filen `compose.flutter.yml`. Viktigt: - `docker compose build ...` bygger bara image. - `docker compose up -d ...` kravs alltid for 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.yml` lamnas orord; Flutter ligger i separat override-fil. - Flutter-web anvander same-origin API (`/api`) for att undvika mixed-content. - Intern Caddy i Flutter-container hanterar static hosting + `/api` proxy. - Feature migration sker stegvis enligt [next_steps_flutter.md](next_steps_flutter.md). ## Kanda fallgropar - Om `recipe-flutter` inte ar i `proxy` natverket blir det 502 fran extern Caddy. - Om browser visar gammal JS kan gamla API-URL:er leva kvar i cache/service worker. - Login med email fungerar inte om backend forvantar username. - `recipe-flutter` kan stoppas av `docker compose down --remove-orphans` om kommandot kors utan override-filen och Flutter-sparat tidigare varit uppe. - En orphan-varning for `recipe-flutter` ar normalt forvantad nar man kor huvudappen med bara `compose.yml`; det betyder inte att backend eller Prisma ar 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 kor ett kommando som bara anvander `compose.yml`. Detta ar normalt och ofarligt sa lange du vet vilken stack du avser att kora. Om `test.gynther.se` slutar svara efter städning med `--remove-orphans`, starta om Flutter-sparet 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.md` fö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. ---