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.
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.
- 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
- 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)
- 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
Neue SettingsKey<T> sealed class mit typisierten StringKey-Objekten,
Default-Werten und Compile-Time-Checks auf gueltige Keys. Alte String-
basierte API bleibt fuer Import/Export-Filter (SENSITIVE_KEYS) erhalten.
SettingsRepository: Neue typisierte Methoden getString(), getStringOrNull(),
setString(), observeString() mit SettingsKey.StringKey-Parameter.
Alle Caller migriert:
- SettingsViewModel: KEY_*-Companion entfernt, StringKey.* direkt genutzt
- CameraViewModel, DashboardViewModel, InventoryPickerViewModel: analog
- SyncServiceImpl: KEY_*-Companion entfernt, getString()/setString()
- ItemRepositoryImpl, MessageRepositoryImpl: getString()/getStringOrNull()
Tests: Alle FakeSettingsRepository-Klassen um typisierte Methoden
erweitert, Mock-Calls in CameraViewModelTest und SyncServiceImplTest
angepasst, 5 neue Tests fuer typisierte API in SettingsRepositoryImplTest.
72 Tests gruen.
Closes#82
ItemListScreen: Suchleiste (OutlinedTextField) mit Search/Clear-Icons,
FilterChipRow mit Dropdown-Menüs für Kategorie, Lagerort und
Ablaufstatus (Abgelaufen/Bald ablaufend/OK). Leere Ergebnisse
zeigen 'Keine Items gefunden'. Suche durchsucht Name und Notizen
(case-insensitive), alle Filter sind kombinierbar.
ItemListViewModel: FilterState (MutableStateFlow) mit searchQuery,
categoryId, locationId, expiryFilter. combine() mit 4 Flows,
lokale Filterung auf gemappten ItemUiModel-Instanzen.
ItemUiModel: locationId und notes mit Defaults hinzugefügt.
13 neue Unit-Tests für Suche, Filter, Kombinationen und
Leerzustände. Alle 257 Tests grün.
Closes#76
WebSocketClientImpl:
- Jitter (±25%) zum exponentiellen Backoff hinzugefügt, um
Thundering-Herd-Effekt bei Server-Neustart zu vermeiden
- Retry-Zähler mit MAX_RETRIES=5: nach 5 konsekutiven Fehlschlägen
wird ConnectionFailed-Event emittiert (nach ~62s)
- Client versucht weiterhin im MAX_BACKOFF-Intervall (60s) zu
reconnecten, gibt nicht vollständig auf
- Zähler wird bei erfolgreicher Verbindung zurückgesetzt
WebSocketEvent: neues ConnectionFailed(message) Event hinzugefügt
SettingsViewModel: ConnectionFailed -> SyncStatus.Error,
Connected -> SyncStatus.Idle (Fehler wird beim Reconnect gelöscht)
Closes#73
Sensitive Keys (auth_access_token, auth_refresh_token, auth_username,
auth_user_id, openai_api_key) werden jetzt ueber EncryptedSharedPreferences
gespeichert statt als Klartext in der Room-Settings-Tabelle.
Neue Dateien:
- SecureTokenStorage: Interface fuer sichere Key-Value-Speicherung
- EncryptedPrefsTokenStorage: Implementierung mit AndroidX Security Crypto
- SecurityModule: Hilt-Provider fuer SecureTokenStorage
Aenderungen:
- SettingsKeys: SENSITIVE_KEYS Set definiert welche Keys verschluesselt werden
- SettingsRepositoryImpl: Routet sensitive Keys an SecureTokenStorage,
nicht-sensitive weiterhin an Room DAO
- ImportExportRepositoryImpl: Filtert sensitive Keys bei Export und Import
- SettingsRepositoryImplTest: 4 neue Tests fuer Secure-Storage-Routing
Closes#72
Migrations.kt: KDoc für MIGRATION_2_3 und MIGRATION_3_4 ergänzt.
KrisenvorratDatabaseMigrationTest: MIGRATION_3_4 in alle Testhelfer
aufgenommen, createV3Database() + openMigratedDbV4() hinzugefügt,
Tests für v3→v4 (messages-Tabelle) und v1→v4 Full-Path-Migration
ergänzt. freshInstall-Test registriert jetzt alle Migrationen.
docs/migration-guide.md: Entwickler-Leitfaden mit Checkliste,
SQLite-Einschränkungen und Testanleitung.
fallbackToDestructiveMigration() war bereits entfernt; dieses Ticket
stellt sicher, dass alle Migrationspfade getestet und dokumentiert sind.
Closes#71
Closes#66
Server (Serialization.kt):
- ContentNegotiation explizit mit charset=UTF-8 konfiguriert, damit
Response-Header immer 'application/json; charset=utf-8' enthält
App (ChatScreen.kt):
- Emoji-Button (Face-Icon) zur MessageInputBar hinzugefügt, der bei Klick
den Fokus auf das Eingabefeld setzt und die Soft-Keyboard öffnet (System-
IME mit Emoji-Panel-Zugang)
- FocusRequester + LocalSoftwareKeyboardController integriert
Tests:
- Utf8MessagingTest (Server): 6 Tests für Umlaute, Emojis, Multi-Codepoint-
Emojis, gemischte Nachrichten, Konversationsabruf, charset-Header
- ChatViewModelTest (App): 4 neue Tests für Umlaut-, Emoji- und gemischte
Nachrichten
- run-integration-tests.ps1: Szenario 6a mit 5 Testfällen (Umlaute, Emojis,
gemischt, Konversation, WebSocket-Delivery mit UTF-8)
- 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
- 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
kcalPerKg-Umbenennung und minStock-Entfernung haben den Identity-Hash
geändert. fallbackToDestructiveMigration() greift nur bei Version-
wechsel – daher version=1→2 notwendig.
- 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)
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.
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
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
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
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
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
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
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
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
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
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
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