Commit graph

103 commits

Author SHA1 Message Date
Jens Reinemann
64ebb737d1 chore: add ADMIN_MESSAGE_TOKEN to VPS docker-compose config 2026-05-18 14:13:52 +02:00
Jens Reinemann
ca6cfbfad9 feat: add plaintext admin message endpoint for testing
- New POST /api/admin/send-message endpoint (admin-only)
- Messages prefixed with [PLAINTEXT] bypass E2EE decryption
- App recognizes [PLAINTEXT] marker and strips it before display
- Allows easy chat testing without E2EE key management
2026-05-18 13:56:15 +02:00
Jens Reinemann
30e86bb7e0 feat(notifications): request POST_NOTIFICATIONS permission on app start 2026-05-18 13:54:03 +02:00
Jens Reinemann
c39bc5e485 feat: foreground service for background message notifications 2026-05-18 13:45:06 +02:00
Jens Reinemann
38394c6350 chore: release v1.7 (12) 2026-05-18 13:23:09 +02:00
Jens Reinemann
e43c0ebbb5 feat(update): UpToDate-Status mit temporärem Feedback anzeigen
- Neuer UpdateStatus.UpToDate State
- Button zeigt 'Keine Updates gefunden' für 5 Sekunden
- Mindestens 1s Checking-Anzeige (vermeidet Flicker bei schneller Antwort)
- UpdateBanner blendet UpToDate-Status aus
2026-05-18 13:04:12 +02:00
Jens Reinemann
1492fa879b refactor(ui): Update-Status-Anzeige in Settings überarbeiten
- Einzelne Buttons pro Status statt gemeinsamer 'Auf Updates prüfen'-Button
- Available-Status als prominenter Button mit Icon
- Checking/Downloading als disabled OutlinedButton
- Error-Status mit 'Erneut prüfen'-Button
- Hidden-Status zeigt den Check-Button
2026-05-18 12:48:04 +02:00
Jens Reinemann
5eae3a4813 chore: release v1.7 (11) 2026-05-18 12:34:58 +02:00
Jens Reinemann
292c538d45 fix(ui): close BottomSheet when 'Alle zurücksetzen' is tapped 2026-05-18 12:29:49 +02:00
Jens Reinemann
ad0945ec3c feat(ui): replace filter chips with BottomSheet + sort options
- Filter button with badge next to search bar
- BottomSheet with filter dropdowns (Kategorie, Lagerort, Ablauf)
- Sort options: Name, Ablaufdatum, Menge (asc/desc)
- 'Alle zurücksetzen' button to clear filters + sort
- docs(genome): Konzept nach .github/genome/ verschoben
2026-05-18 12:27:28 +02:00
Jens Reinemann
6a3009569b fix(ui): filter chip label too long – show only selected value 2026-05-18 12:16:35 +02:00
Jens Reinemann
7ea7729f96 fix: version display format -> three-number (e.g. 1.7.10) without v prefix 2026-05-18 12:16:01 +02:00
Jens Reinemann
461fca7ead chore: release v1.7 (10) 2026-05-18 12:09:11 +02:00
Jens Reinemann
09e01dff00 style: Beton & Stahl Theme - höhere Kontraste, stahlblaue Surfaces 2026-05-18 12:08:06 +02:00
Jens Reinemann
8459705bb1 chore: release v1.7 (9) 2026-05-18 11:46:28 +02:00
Jens Reinemann
bb578c5076 chore: version bump 1.6 (7) -> 1.7 (8), CI Deploy-Workflow redesign
- Version 1.7 (versionCode 8) deployed auf bollwerk.online
- android-ci.yml: nur workflow_dispatch, kein auto-Trigger
- Auto-Versionierung: versionCode wird im CI hochgezählt
- CI deployed direkt auf VPS via SCP + API-Call
- VPS: BOLLWERK_ADMIN_TOKEN konfiguriert (einmalig)
Benötigte Secrets in GitHub: BOLLWERK_ADMIN_TOKEN, VPS_SSH_PRIVATE_KEY
2026-05-18 11:09:26 +02:00
Jens Reinemann
ea3bd6dc97 fix(sync): robuste WebSocket-Verbindung und Token-Refresh
- Backoff nur nach stabiler Verbindung (>30s) zurücksetzen
  → verhindert rapiden 2s-4s-2s-Reconnect-Oscillation
- VIOLATED_POLICY Close-Reason erkennen → AuthRejected-Event
  → kein endloser Retry mit abgelaufenem Token
- Token-Refresh bei AuthRejected: MainViewModel refresht Access-Token
  und reconnectet WS automatisch; bei Fehlschlag Session-Expired
- executeItemRequest: fehlende 401-Retry-Logik ergänzt (Bug 4)
- SyncService.refreshAccessToken() als neue Interface-Methode
2026-05-18 10:48:46 +02:00
Jens Reinemann
887cdbd3f7 feat(settings): server-sync UI aufräumen (#108)
- Login-Status + Logout auf eigene ElevatedCard
- Logout-Button als OutlinedButton (Material-Button statt TextLink)
- Letzte Sync direkt unter Verbindungsstatus ohne Divider
- Refresh-IconButton neben Serverstatus entfernt
- Server-URL Reset nur sichtbar wenn nicht-default Adresse
- Manuelle Sync-Buttons entfernt (vollautomatisch)
2026-05-18 10:09:58 +02:00
Jens Reinemann
bdd8cb4b11 feat(#106): category tap on dashboard navigates to inventory with filter
- Change Screen.ItemList from data object to data class with optional categoryId
- DashboardScreen: make CategoryCard clickable with onCategoryClick callback
- ItemListViewModel: read initial categoryId from SavedStateHandle
- BollwerkNavGraph: wire category click to navigate with categoryId
- Add test for initial category filter from navigation args
2026-05-18 10:01:14 +02:00
Jens Reinemann
8e7352dcc4 feat(security): replace CleartextKeysetHandle with AndroidKeysetManager (#105)
- Extract PrivateKeysetStore interface for testability
- Add AndroidKeystorePrivateKeysetStore (Android Keystore-backed AEAD)
- Refactor E2EEKeyManager to use PrivateKeysetStore
- Add legacy migration: old cleartext key is removed, forcing re-generation
- Update DI module to provide AndroidKeystorePrivateKeysetStore
- Adapt unit tests with FakePrivateKeysetStore + migration test

Private key material no longer appears as cleartext JSON on the JVM heap.
Existing devices with legacy keys will re-generate and re-upload via
EnsureKeyPairUseCase on next app launch.
2026-05-18 09:51:24 +02:00
Jens Reinemann
24c6fac0f8 feat(messaging): push notifications for incoming messages (#104)
- Add NotificationHelper with channel creation, grouped notifications,
  and deep-link PendingIntent into chat
- Trigger notification from MessageRepositoryImpl on WebSocket NewMessage
- Active-chat suppression in ChatViewModel (no notification for current chat)
- Deep-link from notification tap: MainActivity handles intent extras,
  MainScreen navigates to correct Chat screen
- Add POST_NOTIFICATIONS permission to AndroidManifest
- Add notification icon drawable (ic_notification_message)
- Add unit tests for notification suppression logic
- Fix pre-existing test compilation (SyncServiceImplTest missing authEventBus)
2026-05-18 09:39:39 +02:00
Jens Reinemann
c771aa9547 feat(messaging): enforce 10 MB mailbox limit per receiver with FIFO eviction (#103)
- Add getUndeliveredStorageBytes() and evictOldestUndelivered() to MessageRepository
- Check mailbox size before saving; evict oldest undelivered messages if over 10 MB
- Return systemMessage in SendMessageResponse when eviction occurs
- App parses systemMessage and displays it in the sender's conversation
- Add SendMessageResponse to shared module for server/app interop
- Update existing tests to use new response format
- Add 3 new tests for eviction behavior
2026-05-18 09:17:15 +02:00
Jens Reinemann
6a8ffa17be feat(messaging): remove non-functional emoji button (#102) 2026-05-18 08:45:34 +02:00
Jens Reinemann
dad15b9e94 security: WebSocket Auth-Token aus Query-Parameter in Authorization-Header verschieben
- Client: Token als 'Authorization: Bearer' Header statt ?token= Query-Parameter senden
- Server: Token aus Authorization-Header statt Query-Parameter lesen
- Tests: Alle 8 WebSocket-Tests auf Header-Auth umgestellt
- Integration-Tests: WebSocket-Verbindung mit Header aktualisiert

Closes #97
2026-05-18 08:23:10 +02:00
Jens Reinemann
dad2907481 feat: WebSocket-Lifecycle und Sync ab App-Start unabhaengig von Settings-Screen
- MainViewModel: verbindet WebSocket beim App-Start (connectOnStartup) und
  nach Login (via AuthEventBus.loginSuccess). Behandelt alle WebSocket-Events
  (Connected/FullSyncRequired/InventoryUpdated) -> pullSync/pushSync.
  Auto-pushSync wenn Server leer ist und lokale Daten vorhanden (Daten-Recovery).
- AuthEventBus: loginSuccess-Signal ergaenzt (serverUrl + token)
- SyncServiceImpl: emittiert loginSuccess nach erfolgreichem Login
- SettingsViewModel: WebSocket-Lifecycle entfernt (nur noch ConnectionFailed
  fuer UI-Fehlermeldung). Manueller Sync-Button bleibt erhalten.
- WebSocketClientImpl: vollstaendiges Logging, wiederholende User-Benachrichtigung
  bei Verbindungsfehlern (alle MAX_RETRIES Versuche statt nur einmalig)
2026-05-18 01:17:47 +02:00
Jens Reinemann
575c0ad709 feat: automatic forced logout on expired session
When both access and refresh token are invalid (401 on /auth/refresh),
the app now automatically logs out and navigates to Settings (login form).
No data loss - only auth tokens are cleared, local inventory data is intact.

- AuthEventBus: singleton SharedFlow that signals session expiry
- MainViewModel: observes bus, calls logout + disconnect, navigates to Settings
- MainScreen: LaunchedEffect collects navigateToSettings event
- MessageRepositoryImpl: emits session expired when refresh fails
- SyncServiceImpl: emits session expired when refresh fails
2026-05-18 00:55:25 +02:00
Jens Reinemann
a14c40d756 fix: 401 token refresh in MessageRepositoryImpl
fetchUsers() and attemptSendToServer() had no retry logic on 401.
SyncServiceImpl already had this pattern (auto-refresh on 401).

Add private refreshAccessToken(serverUrl) and retry once on 401
in both methods.
2026-05-18 00:44:58 +02:00
Jens Reinemann
ea02029dbe fix: SettingsKey circular init crash on app start
SENSITIVE_KEYS and SENSITIVE_KEY_STRINGS used eager initialization in the
companion object. When E2EEKeyManager.hasKeyPair() was the first access to
SettingsKey, it triggered SettingsKey.<clinit> which tried to resolve
StringKey.E2EEPrivateKeyset - but that class was already 'in initialization
by the current thread' (JVM spec). The JVM returned null, causing NPE in
SENSITIVE_KEY_STRINGS.map { it.key }.

Fix: use by lazy for both properties to defer initialization past <clinit>.
2026-05-18 00:39:32 +02:00
Jens Reinemann
d02a38455b chore: version bump 1.5 (6) -> 1.6 (7) 2026-05-18 00:32:53 +02:00
Jens Reinemann
8c0db56223 feat: E2EE Messaging mit Tink HPKE (X25519 + ChaCha20-Poly1305)
Closes #96

## App

- E2EEKeyManager: Tink HPKE Schlüsselpaar generieren, privaten Key
  via EncryptedSharedPreferences sichern, Nachrichten verschlüsseln
  und entschlüsseln (X25519 + ChaCha20-Poly1305)
- EnsureKeyPairUseCase: Keypair-Initialisierung beim App-Start;
  Public Key via HTTP PUT an Server übermitteln
- MainActivity: EnsureKeyPairUseCase.execute() in onCreate
- SettingsKey: E2EEPrivateKeyset + E2EEPublicKeyBase64 als SENSITIVE_KEYS
- MessageRepositoryImpl: sendMessage verschlüsselt Body mit Empfänger-
  Public-Key; eingehende Nachrichten werden lokal entschlüsselt und
  als Klartext in Room gespeichert; Public-Key-Cache (in-memory) +
  key_updated Handler
- WebSocketClient: KeyUpdated Event hinzugefügt
- WebSocketClientImpl: key_updated Frame parsen; Exception-Logging
- Tink 1.15.0 als neue Dependency

## Server

- V4 Flyway Migration: ALTER TABLE users ADD COLUMN public_key TEXT
- Tables.kt: publicKey Feld in Users-Objekt
- UserRepository: getPublicKey() / setPublicKey()
- UserRoutes: PUT /api/users/{id}/public-key (Auth + Owner-Check +
  Längenvalidierung ≤ 10.000 Zeichen) und
  GET /api/users/{id}/public-key
- WebSocketManager: notifyKeyUpdated() Broadcast an alle anderen
  verbundenen Clients
- MessageRepository: EncryptionService für message body bypassed –
  Server speichert E2EE-Ciphertext direkt (Zero-Knowledge)

## Tests

- E2EEKeyManagerTest: 5 Tests (Roundtrip, Nonce-Uniqueness,
  Wrong-Key, hasKeyPair)
- EnsureKeyPairUseCaseTest: 4 Tests (generate+upload, skip wenn
  vorhanden, kein Upload ohne UserId, kein Crash bei Server-Fehler)
- MessageRepositoryImplTest: 5 neue E2EE-Tests

## Docs

- docs/migration-guide.md: E2EE-Einschränkungen dokumentiert
  (Pending-Message Klartext in SQLite)

## Follow-up

- #105: E2EE Private Key – AndroidKeysetManager statt
  CleartextKeysetHandle (Security Hardening)
2026-05-18 00:22:28 +02:00
Jens Reinemann
9631ec9a92 chore: ungestagede Aenderungen und neue Docs committen 2026-05-17 22:51:07 +02:00
Jens Reinemann
d00b5b245a chore: version bump 5 -> 6 (1.4 -> 1.5) 2026-05-17 22:47:13 +02:00
Jens Reinemann
045a4b7674 feat: Migration-Safety – Room v7, AutoMigration, Flyway, kein fallbackToDestructiveMigration (#99)
- fallbackToDestructiveMigration() aus DatabaseModule entfernt
- BollwerkDatabase auf Version 7 gebumpt
- AutoMigration(from=5, to=6) und (from=6, to=7) definiert
- MigrationTestHelper-Test migrate6To7_preservesData implementiert
- 7.json Schema-Export generiert
- Server: Flyway 9.22.3 integriert (baselineOnMigrate=true)
- V1__initial_schema.sql + V2__cleanup_user_id.sql angelegt
- Skill android-db-migration erstellt
- versionCode 5 / versionName 1.4
2026-05-17 21:17:24 +02:00
Jens Reinemann
3d7c01cef5 feat(update): AlertDialog bei verfuegbarem Update anzeigen
- AlertDialog in MainScreen zeigt verfuegbare Version mit Bestaetigung
- UpdateBanner blendet bei UpdateStatus.Available aus (Dialog uebernimmt)
- FEATURE_CAMERA_ENABLED temporaer deaktiviert

fix(server): Logo-Pfad und statische Ressourcen bereinigen

- /res-Route fuer classpath-Assets (logo.png etc.) hinzugefuegt
- Logo-Pfad von /static/logo.png auf /res/logo.png korrigiert
- Build-Nummer aus Versionsanzeige auf der Homepage entfernt

ci: GitHub Actions Workflow fuer Swift-Tests hinzugefuegt

style: Tabellenformatierung im Code-Reviewer-Agenten bereinigt
2026-05-17 20:52:47 +02:00
Jens Reinemann
9ff21cbc4b ui: Server-Synchronisierung als ElevatedCard neu gestaltet
- Status-Dot + Verbindungstext links, Sync-Button (Refresh-Icon) rechts
  in einer kompakten Zeile statt verstreuter Einzelelemente
- Bei Disconnect: Haupttext 'Keine Verbindung', Countdown als
  zweite Zeile darunter (statt alles in einer langen Zeile)
- Sync-Spinner ersetzt den Button solange Sync läuft
- Aktivitätsmeldung animiert unter der Statuszeile
- Letzte-Sync-Zeit mit Divider am Card-Boden, klar abgetrennt
- 'Synchronisierung erfolgt automatisch.' entfernt (redundant)
2026-05-17 20:35:15 +02:00
Jens Reinemann
fdc016c786 fix: Differenzierte Sync-Aktivitätsmeldungen
- Neues SyncActivityMessage.CheckingForChanges 'Prüfe auf Änderungen…'
  wird beim initialen Catch-up nach Connect angezeigt (statt dem
  irreführenden 'Empfange Inventar-Update…')
- Neues SyncActivityMessage.NoChanges 'Keine Änderungen'
  wird angezeigt wenn der initiale Sync keine Daten zurückliefert
- Echter Server-Push (InventoryUpdated/FullSyncRequired) zeigt weiterhin
  'Empfange Inventar-Update…' → jede echte Kommunikation bleibt sichtbar
2026-05-17 20:27:49 +02:00
Jens Reinemann
aafb9ddd64 fix: Verbindungs-Noise unterdrücken
- loadSettings() ruft connect() nur noch auf wenn ConnectionState
  NotConfigured oder Disconnected ist – verhindert den self-triggering
  Loop (pullSync → loadSettings → connect → Connecting → Connected → …)
- pullSync() erhält Parameter silent=true für den initialen Connect-Sync;
  zeigt kein 'Empfange Inventar-Update…' mehr beim bloßen Verbindungsaufbau
- Version 1.2 (3) → 1.3 (4)
2026-05-17 19:05:37 +02:00
Jens Reinemann
e73d3a11a0 ci: disable automatic CI triggers on push/PR
Both android-ci.yml and ci.yml now only run via workflow_dispatch
(manual trigger). Automatic builds on push/PR are disabled to stop
failing pipeline notifications.
2026-05-17 18:23:27 +02:00
Jens Reinemann
e0130910af chore: migrate server URLs from IP to bollwerk.online domain
Replace all HTTP references to 195.246.231.210 with bollwerk.online
across skills, prompts, scripts, and app default settings:

- Dockerfile: rename KRISENVORRAT_JWT_SECRET to BOLLWERK_JWT_SECRET
- SettingsKey.kt: default server URL now http://bollwerk.online:8080
- publish SKILL/prompt/script: HTTP URLs updated to bollwerk.online
- vps-deploy SKILL: Admin-UI and health-check URLs updated
- run-integration-tests.ps1: default BaseUrl updated

SSH commands (ssh/scp) intentionally kept on IP, as DNS is not
used for SSH access.
2026-05-17 18:22:17 +02:00
Jens Reinemann
a5f89e6a69 rename: Krisenvorrat -> Bollwerk
- Package: de.krisenvorrat.* -> de.bollwerk.*
- Klassen: KrisenvorratApp/Database/Theme -> Bollwerk*
- ApplicationId: de.bollwerk.app
- Server: BOLLWERK_* Env-Vars, bollwerk HOCON-Config
- Docker: bollwerk-server/db/backup Container-Namen
- Room DB: bollwerk.db, SharedPrefs: bollwerk_secure_prefs
- Export-Dateien: bollwerk_export/inventar
- UI-Strings, HTML, Admin-UI: alle auf Bollwerk
- Docs, Skills, README angepasst
- Alle Tests gruen, Build erfolgreich
2026-05-17 17:44:02 +02:00
Jens Reinemann
46bfaa0367 chore: Logo überarbeiten – neue Ratte mit Plättchenpanzer & Patronengurt
- Altes Vektor-Foreground (IKEA Gosig-Style) durch PNG-basiertes Logo ersetzt
- Neue Ratte: grau, illustrativ, bronzener Plättchenpanzer, Patronengurt
- Adaptive-Icon-Foreground als PNG in allen Density-Stufen (mdpi–xxxhdpi)
- Legacy-Launcher-Icons mit dunklem Hintergrund (#16140F)
- Hintergrund von Olivgrün (#3D5229) auf App-Theme-Dunkel (#16140F) geändert
- Web-Logo auf Download-Seite eingebunden (/static/logo.png)
- Quelldatei unter docs/ratti.png archiviert

Closes #93
2026-05-17 17:14:11 +02:00
Jens Reinemann
0fb1ebbdca style: App-UI an Admin-Bereich angleichen (#92)
- Color.kt: Farbpalette von Olivgrün auf Admin-Palette umgestellt
  (burnt orange #C1440E primary, warm beige #E8D5B0 text,
   dark brown #16140F background, tan #A89070 labels)
- Type.kt: Neue Typography mit Monospace für Headings/Titles/Labels
  (analog Admin Share Tech Mono), system-ui für Body
- Theme.kt: KrisenvorratTypography eingebunden
- Dark Mode vollständig spezifiziert, Admin-konsistent
- Alle bestehenden Screens profitieren automatisch via MaterialTheme
- Build , alle Tests grün (70 tasks)
2026-05-17 16:51:46 +02:00
Jens Reinemann
47a2865b34 feat: Sync-Statusanzeige mit Live-Verbindungsstatus und Aktivitaets-Feed (#94)
- ConnectionState sealed interface (Connected/Connecting/Disconnected/NotConfigured)
- WebSocketClientImpl: connectionState StateFlow mit Reconnect-Countdown-Timer
- SyncActivityMessage: Aktivitaets-Feed-Modell mit 3-Sek-Auto-Dismiss
- PendingSyncOpDao: getCount() Flow fuer Queue-Groesse
- SettingsUiState: SyncStatus durch ConnectionState + SyncActivity ersetzt
- SettingsViewModel: observeConnectionState, observePendingQueueCount,
  showActivity mit Job-basiertem Timer-Reset
- SettingsScreen: Farbiger Punkt + Statustext, AnimatedVisibility
  fuer Aktivitaets-Feed, Countdown bei Disconnected
- Alle 306 Tests gruen
2026-05-17 16:02:55 +02:00
Jens Reinemann
5434c00f20 feat: automatischer Sync nach Login/Reconnect, manuelle Push/Pull-Buttons entfernen
- WebSocket Connected-Event löst jetzt automatisch pullSync() aus
  (nach Login = Full Sync, nach Reconnect = inkrementell)
- Push/Pull-Buttons ersetzt durch Hinweis 'Synchronisierung erfolgt
  automatisch' + Fallback-Button 'Jetzt synchronisieren'
- ServerUrl: Default-Wert (VPS-IP) als Konstante in SettingsKey,
  Reset-Button neben dem URL-Feld
- SettingsUiState: serverUrl Default = DEFAULT_SERVER_URL
- Tests angepasst (SettingsRepositoryImplTest)
2026-05-17 15:36:11 +02:00
Jens Reinemann
1df2d1cff5 refactor: manuelle DB-Migrationen durch Room AutoMigration ersetzen
- DB-Version auf 6 hochgezaehlt (Clean-Slate, keine Rueckwaertskompatibilitaet)
- Alle manuellen Migrationen (v1-v5) aus Migrations.kt entfernt
- DatabaseModule: addMigrations() durch fallbackToDestructiveMigration() ersetzt
- migration-guide.md: AutoMigration-Workflow dokumentiert
- Instrumentierte Tests: alte Migrationstests durch frische DB-Tests ersetzt
- Schema 6.json exportiert

Closes #89
2026-05-17 11:43:27 +02:00
Jens Reinemann
5e9c072b51 refactor: kcalPerKg -> kcalPerUnit (kcal pro Einheit)
- ItemEntity, ItemDto: kcalPerKg -> kcalPerUnit (kcal_per_unit)
- Room DB: version 4 -> 5, MIGRATION_4_5 hinzugefuegt
- CalculateSupplyRangeUseCase: Berechnung vereinfacht zu
  quantity * kcalPerUnit (keine Einheitenumrechnung mehr noetig,
  alle Einheiten unterstuetzt)
- ItemFormScreen: Label 'kcal / kg' -> 'kcal / Einheit'
- CsvExporter: Header 'kcal/kg' -> 'kcal/Einheit'
- OpenAiVisionService: Prompt-JSON-Feld angepasst
- Server Tables + InventoryRepository: kcal_per_unit
- Alle Tests aktualisiert und gruen
2026-05-17 11:29:39 +02:00
Jens Reinemann
28b7e83297 refactor: Bottom-Navigation auf custom Surface+Row+IconButton umstellen
Material NavigationBar/NavigationBarItem hat hardcoded interne Paddings
(16dp oben/unten + 32dp Indicator), die bei height-Constraints die Icons
proportional schrumpfen lassen. Custom-Lösung mit:
- Surface + NavigationBarDefaults.containerColor/Elevation
- windowInsetsPadding(NavigationBarDefaults.windowInsets) für System-Insets
- Row mit IconButtons (standard 48dp Touch-Target, 24dp Icons)
- padding(vertical = 4.dp) statt 24dp Material-Default
Icons behalten volle Größe, Bar ist deutlich kompakter.
2026-05-17 11:05:05 +02:00
Jens Reinemann
4397159d62 fix: NavigationBar-Höhe auf 56dp fixieren 2026-05-17 10:59:48 +02:00
Jens Reinemann
32ed321df2 feat: Admin-Statistiken pro Inventar & Inventar-Tabelle mit Paging/Sortierung/Filter/Suche
- Neues DTO: InventoryStatsPerInventoryDto (inventoryId, inventoryName,
  totalItems, totalLocations, totalCategories, recentTransactions,
  lastUpdated, userCount)
- InventoryRepository.getStatsPerInventory(): Stats pro Inventar
- Neuer Endpoint: GET /api/admin/stats/inventories (Admin-only)
- Admin-UI: aufklappbarer Bereich Statistiken pro Inventar (sortierbar)
- Admin-UI: Inventar-Karten durch Tabelle ersetzt mit
  - Paging (10/25/50 Eintraege pro Seite)
  - Sortierung per Klick auf Spaltenheader
  - Filter (alle / mit Benutzern / ohne Benutzer)
  - Freitextsuche nach Name oder Inventar-ID
- Tests: 3 neue InventoryRepositoryTests, 3 neue InventoryStatsTests
  (401/403/Inhalt fuer neuen Endpoint), setUp bereinigt alle Tabellen
- Alle 148 Tests gruen
2026-05-17 10:56:22 +02:00
Jens Reinemann
dfa4b37eda feat(update): Update-Dialog, Installation & App-Start-Integration (#85)
- AndroidManifest: REQUEST_INSTALL_PACKAGES Permission hinzugefuegt
- file_paths.xml: cache/update/ Pfad fuer FileProvider ergaenzt
- UpdateUiState: Sealed UI-State (Hidden, Checking, Available, Downloading,
  ReadyToInstall, Error)
- UpdateViewModel: State-Management fuer Update-Check beim App-Start,
  APK-Download mit Fortschrittsanzeige, Installation via FileProvider
- UpdateBanner: Nicht-blockierender animierter Banner mit Versionsanzeige,
  Download-Button, LinearProgressIndicator, Dismiss-Moeglichkeit
- MainScreen: UpdateBanner ueber dem NavGraph integriert
- ApkInstaller Interface + Impl: Testbare Abstraktion fuer APK-Installation
- RepositoryModule: ApkInstaller DI-Binding registriert
- 7 Unit Tests fuer UpdateViewModel (Update verfuegbar, kein Update,
  nicht konfiguriert, Fehler, Download-Fortschritt, Download-Fehler, Dismiss)
- Alle 441 Tests gruen

Closes #85
2026-05-17 05:13:11 +02:00