Load initial 200 messages, auto-load older messages on scroll to top.
Uses cursor-based DAO queries for stable scroll position.
Also fixes pre-existing ackMessage compile errors in test fakes.
Closes#113
Use a single notification ID (9999) for both idle foreground-service state
and incoming-message alerts. Shows sender name for 1 message, summary for
multiple senders. Cancelling resets to idle instead of removing.
Closes#111
- 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
- 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
- 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
- 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)
- 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
- 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.
- 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)
- 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
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
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.
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>.