Commit graph

71 commits

Author SHA1 Message Date
Jens Reinemann
4c2f5f08a4 feat(app): User-Konzept App-Phase - JWT-Auth, Login, WebSocket-Client (#57)
- SettingsKeys: API_KEY entfernt, AUTH_ACCESS_TOKEN/REFRESH_TOKEN/USERNAME hinzugefügt
- SyncService: login() und logout() Interface-Methoden
- SyncServiceImpl: Bearer-Token statt X-API-Key, Auto-Refresh bei 401
- AuthModels: LoginRequest, LoginResponse, RefreshRequest
- WebSocketClient: Interface + Impl mit exponentiellem Backoff
- SettingsViewModel: Login/Logout, WebSocket-Connect, FullSyncRequired auto-pullSync
- SettingsScreen: Login-Formular (Username + Passwort) statt API-Key-Feld
- NetworkModule: WebSocketClient als Singleton gebunden
- Alle Tests gruen (70 Tasks up-to-date)
2026-05-16 19:45:11 +02:00
Jens Reinemann
14631c7327 feat(server): User-Konzept Auth, JWT, Admin-CRUD, WebSocket-Push, Admin-UI (#57)
- Users-Tabelle mit bcrypt-Passwort-Hash
- JWT-Auth (Access + Refresh Token) ersetzt API-Key
- POST /api/auth/login + /api/auth/refresh
- Admin-Endpoints: GET/POST/DELETE/PUT /api/admin/users
- Seed-Admin beim Start (KRISENVORRAT_ADMIN_PASSWORD)
- Inventar-Endpoints user-spezifisch (userId aus JWT)
- PATCH /api/inventory/items/{id} fuer Einzel-Updates
- WebSocket /ws/sync mit Push-Events (inventoryUpdated, fullSyncRequired)
- Minimale Admin-Web-UI unter /admin/ (XSS-sicher)
- Categories/Locations: Surrogate-PK fuer User-Isolation
- Alle Server-Tests gruen (43 Tests)
2026-05-16 19:28:03 +02:00
Jens Reinemann
db72a8b4ad ci: Android CI deaktivieren (nur noch manuell per workflow_dispatch) 2026-05-16 18:58:49 +02:00
Jens Reinemann
504207376f docs: SSH-Public-Key fuer VPS-Zugang hinterlegen 2026-05-16 18:56:07 +02:00
Jens Reinemann
748140acbd docs: VPS-Deploy Skill hinzufügen (1984 Hosting, Docker, SSH) 2026-05-16 18:47:50 +02:00
Jens Reinemann
809e6aa069 feat: FEATURE_CAMERA_ENABLED compile flag für KI-Kamera
- build.gradle.kts: buildConfigField FEATURE_CAMERA_ENABLED (default: true)
- ItemListScreen: Camera-Icon nur wenn Flag gesetzt, onCameraClick optional
- KrisenvorratNavGraph: CameraCapture-Route und Lambda nur wenn Flag gesetzt
- SettingsScreen: KI-Erkennung-Section nur wenn Flag gesetzt
2026-05-16 18:06:12 +02:00
Jens Reinemann
dd571f46fc feat: KI-Kameraerkennung via OpenAI Vision (Issue #48)
- CameraCapture-Screen: Foto aufnehmen, analysieren, Artikel auswählen
- OpenAiVisionService: gpt-4o Vision API via Ktor (buildJsonObject)
- CameraViewModel: @ApplicationContext, Bitmap-Laden, Nav-Event via UiState
- ItemFormViewModel: prefillJson-Route-Parameter, _pendingCategoryName-Matching
- Settings: OpenAI API-Key (OPENAI_API_KEY) speichern/laden
- Screen.CameraCapture + Screen.ItemForm(prefillJson) in NavGraph
- ItemListScreen: PhotoCamera-Icon in TopAppBar
- AndroidManifest: TakePicture braucht keine CAMERA-Permission (Intent-basiert)
- 8 neue CameraViewModel-Tests, 1 neuer ItemFormViewModel-Test (226 Tests grün)
2026-05-16 17:58:08 +02:00
Jens Reinemann
f4b5197b06 infra: DB-Migration-Infrastruktur einrichten (#49)
- fallbackToDestructiveMigration() entfernt (war inakzeptabel)
- addMigrations(MIGRATION_1_2) in DatabaseModule eingetragen
- Migrations.kt: Migration(1,2) mit Tabellen-Neubau fuer SQLite < 3.25
  (kcal_per_100g -> kcal_per_kg, min_stock entfernt)
- exportSchema = true + KSP-Argument room.schemaLocation = app/schemas/
- 2.json Schema-Snapshot eingecheckt (Basis fuer kuenftige Migrationen)
- androidTest-Assets zeigen auf app/schemas/ (fuer MigrationTestHelper)
- KrisenvorratDatabaseMigrationTest: 4 instrumentierte Tests
  - Datenerhalt nach Migration
  - Korrekte Spalten nach Migration
  - Indices nach Migration
  - Fresh-Install ohne Migration
2026-05-16 14:52:06 +02:00
Jens Reinemann
018d8dc7da feat: Monat/Jahr-Picker statt Tages-Datepicker fuer Ablaufdatum (#52)
- ExpiryDateField: DatePicker entfernt, neuer MonthYearPickerDialog
  mit zwei Dropdowns (Monat + Jahr)
- Gespeicherter Wert: letzter Tag des gewaehlten Monats (atEndOfMonth())
- Anzeigeformat: MM/yyyy statt dd.MM.yyyy (ItemFormScreen + ItemListScreen)
- TextField ganzflaechig klickbar (nicht nur Icon)
- Dialog: OK / Entfernen / Abbrechen
- remember fuer DateTimeFormatter und Listenliterale
- Test auf Monatsende-Datum ausgerichtet
2026-05-16 14:39:39 +02:00
Jens Reinemann
b7f27b6f81 feat(item): DatePicker durch Monat/Jahr-Picker ersetzen
- Ablaufdatum wird jetzt als MM/yyyy gespeichert (letzter Tag des Monats)
- DatePickerDialog -> AlertDialog mit zwei Dropdowns (Monat, Jahr)
- Anzeige in ItemListScreen ebenfalls auf MM/yyyy umgestellt
2026-05-16 14:37:54 +02:00
Jens Reinemann
fcc7142ea1 fix: Room DB-Version auf 2 erhöhen nach Schema-Änderung
kcalPerKg-Umbenennung und minStock-Entfernung haben den Identity-Hash
geändert. fallbackToDestructiveMigration() greift nur bei Version-
wechsel – daher version=1→2 notwendig.
2026-05-16 14:33:05 +02:00
Jens Reinemann
8280a9daf9 refactor: kcal/100g -> kcal/kg umbenennen und Mindestbestand entfernen
- ItemEntity, ItemDto: kcalPer100g -> kcalPerKg (kcal_per_kg),
  minStock-Spalte komplett entfernt
- CalculateSupplyRangeUseCase: Formel angepasst (/ 1000.0 * kcalPerKg)
- GetMinStockWarningsUseCase + MinStockWarning: gelöscht
- UI (ItemFormScreen, WarningsScreen, DashboardScreen): Mindestbestand-
  Felder und Warnungsabschnitte entfernt
- ViewModels, UiState, Repository: alle Referenzen bereinigt
- Server (Tables, InventoryRepository): Schema angepasst
- Room: fallbackToDestructiveMigration() hinzugefügt (keine Produktivdaten)
- Alle 434 Tests gruen
2026-05-16 14:19:10 +02:00
Jens Reinemann
395939a4ec feat(categories): Umbenennen, Löschen mit Umzuweisungs-Dialog, letzter Lagerort vorauswählen (#51)
- Kategorien und Lagerorte umbenennen (Edit-Dialog)
- Löschen mit Umzuweisungs-Dialog wenn Artikel referenzieren
- Letzten verwendeten Lagerort im Artikelformular vorausauswählen
- stale lastLocationId-Validierung gegen aktuelle Location-Liste
- !! durch ?.let ersetzt in beiden Screens
- Irreführenden Delete-Dialog-Text korrigiert
- Tests: 20 neue Unit-Tests (Rename, Reassign, dismissReassign, staleLocation)
2026-05-16 14:05:45 +02:00
Jens Reinemann
caf7002406 fix: Seed-Daten beim App-Start prüfen und ggf. einfügen
RoomDatabase.Callback.onCreate() wird bei adb install -r nicht erneut
ausgeführt, da die DB bereits existiert. Kategorien und Orte blieben
deshalb leer.

Lösung: SeedDatabaseUseCase prüft beim App-Start ob Kategorien/Orte
vorhanden sind und befüllt die Tabellen nur wenn sie leer sind.
2026-05-16 13:57:04 +02:00
Jens Reinemann
9cc15ffaad feat(settings): Kinder-Altersgruppen für Kalorienverbrauch (#47)
domain/model/AgeGroup.kt:
- Neues Enum AgeGroup (6 Gruppen: Kleinkind bis Erwachsener) mit Richtwert-kcal
- AgeGroupEntry-Datenklasse (count + kcalPerDay, anpassbar)
- JSON-Serialisierung via kotlinx.serialization
- Migrationspfad: householdSize + kcalPerPerson → ERWACHSENER-Gruppe

domain/model/SettingsKeys.kt:
- Neue Konstanten-Klasse für Settings-Keys (löst SettingsViewModel-Kopplung im DashboardViewModel auf)

CalculateSupplyRangeUseCase: Signatur auf totalDailyKcal vereinfacht
SettingsViewModel/UiState: ageGroups statt householdSize + dailyKcalPerPerson
DashboardViewModel: combine(3 flows), AGE_GROUPS statt 4 separate Flows
SettingsScreen: Altersgruppen-Tabelle statt zwei TextFields
ImportExportRepositoryImpl: Markdown-Export zeigt Altersgruppen-Gesamtkcal

Tests: +8 neue Tests (Migration, Validierung, Fallbacks), alle bestehenden Tests aktualisiert

Closes #47
2026-05-16 13:35:27 +02:00
Jens Reinemann
1236d61543 docs(device): Wireless ADB workflow und Screenshot-Pull dokumentiert
- Wireless ADB als bevorzugten Verbindungsweg dokumentiert
- Pull-Workflow für Screenshots (exec-out liefert über WLAN korrupte Bilder)
- Hinweis: adb -d nur USB, Wireless braucht adb -s <IP>:<PORT>
- Vollständiger Wireless-Deploy-Workflow (Build/Install/Launch)
2026-05-16 12:55:35 +02:00
Jens Reinemann
d97882cfd6 ci: add Android CI/CD workflow
.github/workflows/android-ci.yml:
- Triggers on push to main and pull requests
- Ubuntu-latest runner with Java 17 (Temurin)
- Gradle cache via gradle/actions/setup-gradle
- Jobs: assembleDebug build + unit tests (./gradlew test)
- Uploads debug APK as workflow artifact (14-day retention)

Closes #14
2026-05-14 21:48:16 +02:00
Jens Reinemann
cb576349e0 feat(server): add LAN dev-server integration & end-to-end sync tests
Start scripts (PS1 + Shell), Dockerfile, E2E sync tests, and README
documentation for Phase 2 LAN server deployment.

New files:
- start-server.ps1 / start-server.sh: one-command server startup with
  auto-build, LAN-IP detection, and configurable API key
- Dockerfile: multi-stage build (Gradle → JRE Alpine) for container
  deployment with volume mount for persistent data
- .dockerignore: excludes app/, .git, build artifacts from Docker context
- EndToEndSyncTest.kt: 7 E2E tests covering full push/pull sync cycle,
  multi-client overwrite, empty DB pull, multiple round-trips, and auth
  rejection for unauthenticated requests
- README.md: project overview, build instructions, and complete Phase 2
  server setup docs (4 start options, LAN setup, API reference, security)

Changed files:
- AndroidManifest.xml: added usesCleartextTraffic=true for HTTP in LAN

Closes #46
2026-05-14 21:45:33 +02:00
Jens Reinemann
a972ce34ca feat(settings): add sync UI with server configuration and push/pull actions
Closes #45

SettingsScreen:
- Server-URL and API-Key input fields (API-Key masked as password)
- Push (upload) and Pull (download) sync buttons
- Sync status indicator (running/success/error)
- Last sync timestamp display (persisted via Room settings)

SettingsViewModel:
- Inject SyncService for server communication
- pushSync(): exports local inventory to InventoryDto, uploads via SyncService
- pullSync(): downloads InventoryDto from server, imports into local DB
- Persists server_url, api_key, sync_last_timestamp in Room settings table

ImportExportRepository:
- New methods exportToInventoryDto() and importFromInventoryDto()
- Refactored to eliminate code duplication via buildInventoryDto() and
  applyInventoryDto() helper methods

Tests:
- 8 new ViewModel tests covering sync settings loading, push/pull success,
  push/pull error scenarios, and sync status dismissal
- FakeSyncService and extended FakeImportExportRepository for testing
2026-05-14 21:30:55 +02:00
Jens Reinemann
215790d68e feat(app): add Ktor HTTP client and SyncService for inventory sync
domain/model/SyncError.kt:
- Sealed class with ConnectionError, Timeout, AuthError, ServerError,
  NotConfigured, Unknown subtypes for typed error handling

domain/repository/SyncService.kt:
- Interface with downloadInventory() and uploadInventory() returning
  Result<InventoryDto> for clean error propagation

data/sync/SyncServiceImpl.kt:
- Ktor Client implementation using OkHttp engine
- GET /api/inventory and PUT /api/inventory endpoints
- X-API-Key header authentication matching server contract
- Server URL and API key read from SettingsRepository
- withContext(Dispatchers.IO) for network calls
- Catches SocketTimeoutException, ConnectException specifically

di/NetworkModule.kt:
- Hilt module providing singleton HttpClient with OkHttp engine
- ContentNegotiation with kotlinx.serialization JSON
- Configurable connect/read/write timeouts (10s/30s/30s)
- Binds SyncServiceImpl to SyncService interface

Dependencies:
- ktor-client-core, ktor-client-okhttp,
  ktor-client-content-negotiation, ktor-client-mock (test)
- ktor-serialization-kotlinx-json (shared with server)
- INTERNET permission added to AndroidManifest.xml

Tests: 9 tests with Ktor MockEngine covering success, 401, 500,
missing config, trailing slash URL normalization

Closes #44
2026-05-14 21:14:40 +02:00
Jens Reinemann
cb9bd2bdf4 feat(server): add API-Key authentication for REST endpoints
server/plugins/Authentication.kt:
- Custom Ktor AuthenticationProvider supporting both X-API-Key header
  and Authorization: Bearer <key> for API-Key validation
- ApiKeyPrincipal data class implementing Principal interface
- 401 Unauthorized with ErrorResponse body for missing/invalid keys

server/plugins/Routing.kt:
- Inventory routes wrapped in authenticate(api-key) block
- Health endpoint remains public (no auth required)

server/src/main/resources/application.conf:
- API key configurable via krisenvorrat.apiKey property
- Environment variable override via KRISENVORRAT_API_KEY

server/tests:
- 7 new AuthenticationTest cases (valid bearer, valid X-API-Key,
  missing key, invalid bearer, invalid X-API-Key, PUT without key,
  health without key)
- All existing ApplicationTest cases updated with bearer auth header

Closes #43
2026-05-14 20:50:16 +02:00
Jens Reinemann
5974144621 feat(server): add REST-API endpoints for inventory sync & CRUD
server/src/main/kotlin/.../routes/InventoryRoutes.kt:
- GET /api/inventory: returns full inventory as JSON
- PUT /api/inventory: full-sync replaces entire server inventory

server/src/main/kotlin/.../plugins/StatusPages.kt:
- Structured error handling via Ktor StatusPages plugin
- BadRequestException, SerializationException, IllegalArgumentException → 400
- Unhandled exceptions → 500 with logging

server/src/main/kotlin/.../plugins/CallLogging.kt:
- Request logging via Ktor CallLogging plugin (INFO level)

server/src/main/kotlin/.../model/ErrorResponse.kt:
- Serializable error response DTO (status + message)

server/src/main/kotlin/.../plugins/Routing.kt:
- Health endpoint moved from /health to /api/health
- Inventory routes mounted under /api/inventory

server/src/main/kotlin/.../Application.kt:
- Added configurePlugins() for testability (DB init separate)
- StatusPages and CallLogging plugins configured

server/src/test/.../ApplicationTest.kt:
- 8 endpoint tests using Ktor TestApplication with in-memory H2
- Tests: health, 404, empty GET, PUT valid, PUT+GET roundtrip,
  invalid JSON → 400, data replacement, JSON content type

Closes #42
2026-05-14 20:30:34 +02:00
Jens Reinemann
2387c6ee5a feat(server): add Exposed ORM database layer with H2
server/db/Tables.kt:
- Exposed table definitions for Categories, Locations, Items, Settings
- Schema mirrors Room entities from app module
- Foreign keys on Items referencing Categories and Locations

server/db/DatabaseFactory.kt:
- H2 file-based DB initialization (jdbc:h2:file:./data/krisenvorrat)
- Parameterized for testability (in-memory DB for tests)
- Schema auto-creation via SchemaUtils.create()

server/repository/InventoryRepository.kt:
- Full CRUD: saveInventory() and loadInventory()
- Atomic replace via transaction (deleteAll + insert)
- Direct mapping between Exposed rows and shared DTOs

4 repository tests with H2 in-memory covering:
- Empty DB, full round-trip, overwrite, nullable fields

Closes #41
2026-05-14 20:15:07 +02:00
Jens Reinemann
cb190e61e9 feat(server): add Ktor server module with health endpoint
New Gradle module :server (Kotlin/JVM) with Ktor 3.1.2 framework,
configured as an embedded Netty HTTP server.

server/src/main/kotlin/de/krisenvorrat/server/:
- Application.kt: entry point using EngineMain for HOCON config
- plugins/Routing.kt: GET /health endpoint returning 200 OK
- plugins/Serialization.kt: ContentNegotiation with kotlinx.json

Configuration:
- application.conf (HOCON): host 0.0.0.0, port 8080, module reference
- logback.xml: SLF4J/Logback console logging

Build config:
- server/build.gradle.kts: Ktor plugin with Fat JAR (server.jar)
- libs.versions.toml: Ktor 3.1.2, Logback 1.5.18 dependencies
- settings.gradle.kts: include(:server)
- :server depends on :shared for common DTO models

Tests: 2 tests (health endpoint, 404 on unknown route) via
Ktor testApplication.

Closes #40
2026-05-14 20:06:40 +02:00
Jens Reinemann
c0c4978ccf feat(shared): add shared module with common DTO models
New Gradle module :shared (pure Kotlin/JVM) containing @Serializable
DTO classes for use by both the Android app and future Ktor server.

shared/src/main/kotlin/de/krisenvorrat/shared/model/:
- InventoryDto: root DTO replacing ExportData (version, categories,
  locations, items, settings)
- CategoryDto, LocationDto, ItemDto, SettingDto: extracted from
  the former *Export data classes in :app

Migration in :app:
- ExportData.kt deleted (classes moved to :shared)
- ImportExportRepositoryImpl now imports from de.krisenvorrat.shared.model
- app/build.gradle.kts adds implementation(project(:shared))

Build config:
- libs.versions.toml: added kotlin-jvm plugin entry
- build.gradle.kts (root): registered kotlin-jvm plugin
- settings.gradle.kts: include(:shared)

JSON wire format is unchanged; all 165 existing tests pass.

Closes #39
2026-05-14 19:50:23 +02:00
Jens Reinemann
309587bc36 docs(server-tech): ADR für Server-Technologie – Ktor gewählt
Anforderungen/design/server-tech/adr-server-technology.md:
Architecture Decision Record für die Server-Technologie in Phase 2
(Geräte-Synchronisierung). Ktor gewählt wegen gleicher Sprache
(Kotlin), kotlinx.serialization-Kompatibilität, Code-Sharing-
Möglichkeit, geringem Ressourcenverbrauch und JetBrains-Support.

Geprüfte Alternativen: Spring Boot, Node.js+Express, Python+FastAPI.

Closes #10
2026-05-14 19:22:27 +02:00
Jens Reinemann
ce851af37b feat(settings): add JSON import with SAF file picker
SettingsScreen: Added import button that opens the system file picker
(ActivityResultContracts.OpenDocument) filtered to application/json.
After file selection, a confirmation dialog warns that existing data
will be overwritten. Import result is shown in a success/error dialog.

SettingsViewModel: Added onImportFileSelected(uri), onImportConfirmed(),
onImportDismissed(), onImportResultDismissed() methods. The import reads
the file via contentResolver.openInputStream() and delegates to the
existing ImportExportRepository.importFromJson(). Settings are reloaded
after successful import.

SettingsUiState: Extended with isImporting, importResult (sealed
interface ImportResult with Success/Error), and pendingImportUri for
the confirmation dialog flow.

SettingsViewModelTest: Added 6 unit tests covering import success,
invalid JSON error, empty file, null input stream, dialog state
management, and result dismissal.

Closes #38
2026-05-14 03:35:50 +02:00
Jens Reinemann
8193445939 feat(settings): add JSON/Markdown export via Share Intent
Implement export functionality in the Settings screen allowing users to
share their inventory data as JSON (via FileProvider + ACTION_SEND with
EXTRA_STREAM) or Markdown (via ACTION_SEND with EXTRA_TEXT).

Key changes:
- ShareContent sealed interface for export events (Json with URI,
  Markdown with text)
- SettingsViewModel: exportJson() writes to cache file and creates
  FileProvider URI; exportMarkdown() provides text directly
- SettingsUiState: isExporting, shareContent, exportError fields
- SettingsScreen: LaunchedEffect consumes share events and opens
  Android Share Sheet via Intent.createChooser
- FileProvider registered in AndroidManifest with cache-path config
- MockK added as test dependency for FileProvider static mocking
- 8 new unit tests covering export success, failure, and state cleanup

Closes #37
2026-05-14 03:26:15 +02:00
Jens Reinemann
12f787a406 feat(export): add exportToMarkdown() to ImportExportRepository
Implement Markdown export for the entire inventory (Issue #36).
The method renders categories as headings with items in a table
(Name, Menge, Einheit, MHD, Lagerort). Empty categories are skipped.
Dates are formatted as dd.MM.yyyy (German), quantities use German
decimal format (comma). Settings section shows household_size and
kcal_per_day if present.

Includes 6 unit tests covering: full export, empty categories,
missing expiry date, settings section, fractional quantities, and
irrelevant settings omission.

Closes #36
2026-05-14 03:04:43 +02:00
Jens Reinemann
e85b151cd5 feat(settings): implement SettingsScreen with ViewModel, UI and persistence
Implement the full Settings tab with household size and daily kcal/person
input fields, persisted via Room through the existing SettingsRepository.
The DashboardViewModel now reads settings reactively and passes them to
CalculateSupplyRangeUseCase instead of using hardcoded defaults.

Changes:
- Add observeValue(key) Flow method to SettingsDao and SettingsRepository
- Create SettingsViewModel with load/save logic and input validation
- Create SettingsUiState data class
- Replace SettingsScreen placeholder with full Compose UI
  (household size, kcal/day fields, save button, export/import placeholders)
- Integrate settings into DashboardViewModel via combine() with 4 flows
- Add SettingsViewModelTest (6 tests covering defaults, persistence, validation)
- Update DashboardViewModelTest and test fakes for new constructor parameter

Closes #35
2026-05-14 02:50:44 +02:00
Jens Reinemann
34bd1f603f feat(warnings): implement dedicated Warnings screen with ViewModel
ui/warnings/WarningsScreen.kt: full implementation replacing placeholder.
Shows expiry warnings (colored by urgency: URGENT=error, WARNING=orange)
and min-stock warnings as individual cards in a LazyColumn. Displays
empty state when no warnings exist.

ui/warnings/WarningsViewModel.kt: HiltViewModel observing ItemRepository
flow, delegates to GetExpiryWarningsUseCase and GetMinStockWarningsUseCase.
Exposes WarningsUiState via StateFlow.

ui/warnings/WarningsUiState.kt: data class with expiryWarnings,
minStockWarnings, isLoading, and derived properties (totalWarningCount,
hasWarnings).

ui/dashboard/DashboardScreen.kt: replaced ExpiryWarningsCard and
MinStockWarningsCard with compact WarningsSummaryCard showing only
warning counts. Removed unused domain model imports.

tests: 7 WarningsViewModel unit tests covering empty state, expiry
warnings, min-stock warnings, combined counts, reactive updates.

Closes #34
2026-05-14 02:39:46 +02:00
Jens Reinemann
a4c0dc63b4 feat(navigation): implement Bottom Navigation Bar with 4 tabs and app shell
MainScreen.kt: new app shell with Scaffold + Material 3 NavigationBar
providing 4 tabs (Uebersicht, Inventur, Warnungen, Einstellungen).

TopLevelDestination.kt: enum defining tab routes, icons (Home, Inventory2,
Warning, Settings), and labels for the navigation bar.

Screen.kt: added Warnings and Settings sealed interface members.

KrisenvorratNavGraph.kt: accepts Modifier, added Warnings/Settings
composables, removed obsolete DashboardScreen navigation callback.

DashboardScreen.kt: removed Scaffold wrapper and onNavigateToItems param,
now uses Column layout (TopAppBar handled inline).

ItemListScreen.kt: removed onDashboardClick param and Dashboard menu entry
(no longer needed with tab navigation).

WarningsScreen.kt, SettingsScreen.kt: placeholder screens for future impl.

MainActivity.kt: delegates to MainScreen instead of NavGraph directly.

Added material-icons-extended dependency for Inventory2 icon.

Closes #33
2026-05-14 02:25:47 +02:00
Jens Reinemann
c88d10be10 feat(theme): add Material 3 custom dark theme with seed #4A6741
Color.kt: New file with M3 color tokens generated from olive green
seed color #4A6741. Defines primary, secondary, tertiary, error,
surface, and container colors for the dark color scheme.

Theme.kt: Updated DarkColorScheme with all custom color tokens,
changed default to darkTheme=true, status bar now uses surface
color instead of primary for better edge-to-edge appearance.

themes.xml: Changed splash theme parent from Material.Light to
Material dark, added dark background/status/navigation bar colors
matching the Compose surface color (#1A1C18).

Closes #32
2026-05-14 02:05:05 +02:00
Jens Reinemann
b12684e6fc feat(dashboard): add Dashboard ViewModel & UI with overview, warnings, supply range
Implement the Dashboard screen (Issue #30) as the new start destination:

- DashboardUiState: data class with sections for category summaries,
  total value, supply range, expiry warnings, and min stock warnings
- DashboardViewModel: combines Item/Category flows with all five
  Use Cases from #29 (CalculateCategorySummary, CalculateTotalValue,
  CalculateSupplyRange, GetExpiryWarnings, GetMinStockWarnings)
- DashboardScreen: Material 3 layout with color-coded cards for
  summary overview, supply range (days), expiry warnings (red/orange),
  and min stock warnings (red), plus per-category cards
- Navigation: Dashboard added as startDestination, ItemListScreen
  gets a Dashboard menu entry for back-navigation
- 9 unit tests covering empty state, category summaries, total value,
  supply range, expiry/min-stock warnings, and reactive updates

Closes #30
2026-05-14 01:46:34 +02:00
Jens Reinemann
4cc7a781d2 feat(domain): add dashboard calculation use cases and tests
domain/model/:
- CategorySummary: item count + total value per category
- ExpiryWarning + ExpiryUrgency: expiry date warnings (urgent ≤6mo, warning ≤12mo)
- MinStockWarning: items below minimum stock with deficit

domain/usecase/:
- CalculateTotalValueUseCase: sum of quantity × unitPrice
- CalculateCategorySummaryUseCase: per-category item count and value
- CalculateSupplyRangeUseCase: kcal-based supply range in days
  (weight units g/kg/mg only, defaults 2 persons × 2000 kcal/day)
- GetExpiryWarningsUseCase: items expiring within 6/12 months
- GetMinStockWarningsUseCase: items where quantity < minStock

All use cases are pure functions with @Inject constructor() for Hilt.
39 unit tests covering all calculations including edge cases.

Closes #29
2026-05-14 01:38:32 +02:00
Jens Reinemann
10d19f0321 feat(navigation): implement CRUD navigation and MainActivity integration
ui/navigation/Screen.kt:
- Sealed interface with @Serializable routes: ItemList, ItemForm,
  CategoryManagement, LocationManagement
- ItemForm accepts optional itemId for edit mode (type-safe navigation)

ui/navigation/KrisenvorratNavGraph.kt:
- NavHost with ItemList as start destination
- Screen wiring: ItemList -> ItemForm (create/edit), CategoryManagement,
  LocationManagement with back navigation via popBackStack()

ui/item/ItemListScreen.kt:
- Added onItemClick, onCategoriesClick, onLocationsClick callbacks
- TopAppBar MoreVert dropdown menu for category/location management
- ItemCard now clickable to navigate to edit mode

MainActivity.kt:
- Replaced placeholder with NavHost via KrisenvorratNavGraph
- rememberNavController() as root navigation controller

ui/navigation/ScreenTest.kt:
- 7 tests covering route instantiation, nullable itemId, equality

Closes #28
2026-05-14 01:20:54 +02:00
Jens Reinemann
f0ad946140 feat(item): add ItemFormViewModel and ItemFormScreen for create/edit
ItemFormViewModel:
- Create-Modus (new article) and Edit-Modus (load existing by ID via
  SavedStateHandle navigation argument)
- Form state with all ItemEntity fields as MutableStateFlow
- Validation: name required, quantity > 0, category and location required
- Save function (insert for create, update for edit)
- Loads categories and locations for dropdown selection

ItemFormScreen:
- OutlinedTextField for name, quantity, unit, price, kcal/100g,
  min stock, notes
- ExposedDropdownMenuBox for category and location selection
- Material 3 DatePickerDialog for expiry date (MHD)
- Inline validation error display per field
- Save button in TopAppBar, back navigation on successful save
- UUID generation for new articles

Tests:
- 18 unit tests covering create mode, edit mode, field updates,
  validation (all required fields), and save behavior (insert vs update)

Closes #27
2026-05-14 01:11:36 +02:00
Jens Reinemann
a1cd7e5199 feat(item): add ItemListScreen with grouped display and delete function
ui/item/ItemUiModel.kt:
- UI data class combining entity data with resolved category/location names
- Computed properties isExpired and isExpiringSoon for MHD color coding

ui/item/ItemListViewModel.kt:
- Combines ItemRepository, CategoryRepository, LocationRepository via Flow.combine
- Groups items by category name (sorted alphabetically)
- Delete flow with confirmation dialog state management

ui/item/ItemListScreen.kt:
- LazyColumn with category headers and Material 3 Cards per item
- Shows name, quantity+unit, location, and color-coded expiry date
- Delete via IconButton with AlertDialog confirmation
- Empty state when no items exist
- FAB with onAddItem navigation callback

ui/item/ItemListViewModelTest.kt:
- 9 unit tests covering init, grouping, name resolution,
  delete dialog flow, and alphabetical sorting

Closes #26
2026-05-14 01:03:37 +02:00
Jens Reinemann
a27660fd4a feat(ui): add category and location management screens
Closes #25

ui/category/:
- CategoryListViewModel: StateFlow-based ViewModel with add/delete
  dialog state management, backed by CategoryRepository
- CategoryListScreen: Material 3 Scaffold with LazyColumn, FAB for
  adding, delete confirmation dialog with CASCADE warning

ui/location/:
- LocationListViewModel: same pattern for LocationRepository
- LocationListScreen: same UI pattern for location management

Tests:
- CategoryListViewModelTest: 11 tests covering init, add, delete,
  dialog state, blank name rejection
- LocationListViewModelTest: 11 tests (same coverage)

Dependencies:
- Added lifecycle-runtime-compose for collectAsStateWithLifecycle
- Added kotlinx-coroutines-test for ViewModel unit tests
2026-05-14 00:56:36 +02:00
Jens Reinemann
0c1e06afca test: add unit tests for Room DAOs, LocalDateConverter, and JSON roundtrip
LocalDateConverterTest: added negative test for invalid string input
(DateTimeParseException).

CategoryDaoTest, LocationDaoTest: added getAll tests with multiple
entities to verify complete retrieval.

ItemDaoTest: fixed getExpiringSoon test (was calling non-existent
getExpiringSoon(Int) instead of getExpiringSoonByCutoff(LocalDate));
added getAll, getById positive, and getById negative tests.

JsonRoundtripTest (new): verifies lossless export-import roundtrip
with multiple items covering all fields, nullable fields (null
kcalPer100g, null expiryDate), and empty database edge case.

TestFakes (new): extracted shared Fake DAO implementations from
ImportExportRepositoryImplTest to avoid private class redeclaration
errors across test files in the same package.

Closes #22
2026-05-14 00:32:45 +02:00
Jens Reinemann
5825b0351c feat: JSON-Export & Import (Roundtrip-Serialisierung) #21
- ExportData/CategoryExport/LocationExport/ItemExport/SettingExport mit @Serializable
- ImportExportRepository-Interface (exportToJson/importFromJson: Result<Unit>)
- ImportExportRepositoryImpl mit atomarer Transaktion via DatabaseTransaction
- ignoreUnknownKeys=true + Versions-Check (version==1)
- @Upsert upsertAll() in CategoryDao, LocationDao, ItemDao, SettingsDao
- DI-Binding in RepositoryModule + DatabaseTransaction in DatabaseModule
- 5 Unit-Tests (29 passed total)
2026-05-14 00:20:04 +02:00
Jens Reinemann
b7cc8db80a chore(prompts): add autopilot prompt for iterative ticket processing
autopilot.prompt.md: new agent prompt (Claude Opus 4.6) that works
through the ticket backlog fully automatically. Each cycle:
1. Runs next-ticket.ps1 – stops cleanly when no tickets remain.
2. Invokes nextstep as a fresh, isolated sub-agent (Claude Opus 4.6)
   per ticket; no context is shared between iterations.
3. Commits with Conventional Commits after each successful ticket.
4. Repeats until queue is empty or an unresolvable error occurs.
2026-05-14 00:09:15 +02:00
Jens Reinemann
388532c946 feat(export): add ImportExportRepository with JSON export/import
domain/repository/ImportExportRepository.kt: new interface with
exportToJson() and importFromJson(json) suspend functions.

data/export/ExportData.kt: serializable data class bundling all
entity lists for JSON serialization via kotlinx.serialization.

data/export/ImportExportRepositoryImpl.kt: implementation using
kotlinx.serialization + Dispatchers.IO; exportToJson fetches all
DAOs and serializes, importFromJson deserializes and upserts back.

data/db/dao/{Category,Item,Location,Settings}Dao.kt: added @Upsert
upsertAll() suspend function to each DAO to support bulk import.

di/RepositoryModule.kt: bound ImportExportRepositoryImpl to
ImportExportRepository via Hilt @Binds.

test/.../FakeXxxDao.kt: upsertAll() implemented in all four fake
DAOs for unit test coverage.
2026-05-14 00:09:07 +02:00
Jens Reinemann
4aba9f24a4 chore: Dokumentation, SKILL.md und Drawables aktualisieren 2026-05-13 23:56:56 +02:00
Jens Reinemann
95d8a10ed0 feat(repo): Repository-Schicht implementieren (#20)
- 4 Repository-Interfaces in domain/repository/ (Category, Location, Item, Settings)
- 4 Implementierungsklassen in data/repository/ mit Hilt @Inject
- RepositoryModule mit @Binds-Bindings fuer alle Repositories
- Datumslogik (getExpiringSoon) aus ItemDao in ItemRepositoryImpl verschoben
- 20 Unit-Tests mit Fake-DAOs (4 pro Repository)
2026-05-13 23:50:00 +02:00
Jens Reinemann
7380dbbdea feat(di): Add DatabaseModule with Hilt providers for Room DB and DAOs (#19)
- DatabaseModule: @Module + @InstallIn(SingletonComponent) with @Singleton-scoped
  Room.databaseBuilder provider and four @Provides methods for ItemDao,
  CategoryDao, LocationDao and SettingsDao
- DatabaseModuleTest: smoke-test verifies all four DAO providers return
  non-null objects using an in-memory Room database
2026-05-13 23:31:42 +02:00
Jens Reinemann
b719739451 feat(db): Room-Datenbank & DAOs implementieren (#18)
- KrisenvorratDatabase mit allen 4 Entities und LocalDateConverter
- CategoryDao, LocationDao, ItemDao, SettingsDao mit CRUD und Flow-Queries
- ItemDao.getExpiringSoon(daysUntil) als Default-Interface-Methode
- SettingsDao mit @Upsert (Room 2.6.1)
- Instrumentierungstests für alle 4 DAOs (in-memory DB)
- androidx.room:room-testing zu Dependencies ergänzt
2026-05-13 23:18:48 +02:00
Jens Reinemann
f52724ce0c fix(workflows): Board + Order-Zuweisung in [P]- und [B]-Workflows ergänzen
Beide Workflow-Prompts erstellten Sub-Tickets per gh issue create, setzten
aber weder Board-Zugehörigkeit (gh project item-add) noch Order-Wert
(gh project item-edit). Das führte dazu, dass neue Tickets auf dem Board
ohne Order-Wert blieben und von next-ticket.ps1 nicht gefunden wurden.

Lösung: In workflow-planning.prompt.md (Phase 4) und
workflow-block-planning.prompt.md (Phase 6) einen neuen Pflichtabschnitt
'Board & Order' ergänzt mit den konkreten gh-Befehlen und Verweis auf SKILL.md.
2026-05-13 22:59:56 +02:00
Jens Reinemann
645a110297 feat(#17): Room-Entities & LocalDateConverter
- Add CategoryEntity, LocationEntity, SettingsEntity, ItemEntity
- ItemEntity: FK to Category+Location with CASCADE, indices on FK columns
- LocalDateConverter: LocalDate? <-> String? (ISO-8601) via @TypeConverter
- Add LocalDateConverterTest: 4 unit tests (null/non-null round-trip)
2026-05-13 22:57:43 +02:00
Jens Reinemann
74bf5ef060 fix(gh-tickets): set-board-status akzeptiert 'In Progress' mit Leerzeichen
- ValidateSet entfernt (war zu restriktiv - kein Agent-Fehler mehr bei 'In Progress')
- Normalisierung: 'In Progress' → 'InProgress' via .Trim()/-replace
- Fehlerdetails bei gh-Fehler sichtbar (kein Out-Null mehr)
- Null-Check auf content vor content.number-Vergleich (robuster gegen Draft-Items)
- Board-Fehler mit Exit 1 und Klartext-Meldung
- Getestet: alle 3 Status-Übergänge + 'In Progress' mit Leerzeichen + Fehlerfälle
2026-05-13 22:46:46 +02:00