push notifications

This commit is contained in:
2026-03-03 20:32:53 +00:00
parent 8c0cbac246
commit 276d2f2615
18 changed files with 802 additions and 32 deletions

View File

@@ -0,0 +1,53 @@
# Android Bug Hunter — Project Memory
## Recurring Patterns Found
### NotificationHelper: runBlocking on Service Thread
`NotificationHelper.show()` calls `runBlocking` three times (notificationsEnabled, mutedRooms, roomNameCache).
This blocks the `PushService` callback thread. Fix: pass pre-collected values in or make `show()` a suspend fun.
See: `push/NotificationHelper.kt`
### FluffytrixPushService: Leaking CoroutineScope
`FluffytrixPushService` creates its own `CoroutineScope(SupervisorJob() + Dispatchers.IO)` but never cancels
it when the service is destroyed. Coroutines launched from `onNewEndpoint`/`onUnregistered` can outlive the
service. Fix: cancel scope in `onDestroy()`.
### PushRegistrationManager: OkHttp response body not closed
`registerPusher()` stores `response.body?.string()` (which auto-closes), but the `execute()` call itself is
never inside a `use {}` block — if `body?.string()` throws, the response is not closed.
Fix: wrap in `response.use { ... }`.
### build.gradle.kts: API key in plain BuildConfig
`TENOR_API_KEY` hardcoded in `buildConfigField` — visible in plaintext in the APK's BuildConfig class.
### MainViewModel: `onUpdate` listener launches on `Dispatchers.Default` inside `viewModelScope`
The timeline listener's `onUpdate` callback launches a coroutine with `viewModelScope.launch(Dispatchers.Default)`.
If the ViewModel is cleared mid-flight the scope is cancelled but the `onUpdate` lambda (captured by the
SDK via JNI) can still fire and attempt `launch` on a cancelled scope — harmless but noisy.
### MainViewModel: `CoroutineScope` inside `loadTimeline` shared across closures
A `Mutex` local to `loadTimeline` is captured by the `TimelineListener` lambda. When `loadTimeline` is called
again for a new room, the old listener still holds the old mutex — correct, but produces confusing parallelism.
### MainViewModel: `sendGif` creates a new OkHttpClient per call
A brand-new `OkHttpClient()` is constructed inside `sendGif` on every invocation. Should reuse a shared
client (like the one in `PushRegistrationManager`).
### SettingsScreen: new OkHttpClient per API-key test
A new `OkHttpClient()` is created on every "Save" button press inside `SettingsScreen`. Leaks the connection
pool after the composable is disposed.
### DeepLinkState: global singleton StateFlow — never clears on logout
`DeepLinkState` is a process-level `object`. After logout/re-login the stale `pendingRoomId` can navigate
the new session to the old room.
### PreferencesManager: `notificationsEnabled` default logic is inverted-looking
`prefs[KEY_NOTIFICATIONS_ENABLED] != false` — treats missing key as `true` (desired), but any corruption
storing a non-boolean type would also return `true`. Low risk but worth noting.
## Key File Paths
- Push infra: `app/src/main/java/com/example/fluffytrix/push/`
- ViewModel: `app/src/main/java/com/example/fluffytrix/ui/screens/main/MainViewModel.kt`
- Settings UI: `app/src/main/java/com/example/fluffytrix/ui/screens/settings/SettingsScreen.kt`
- DI: `app/src/main/java/com/example/fluffytrix/di/AppModule.kt`
- Prefs: `app/src/main/java/com/example/fluffytrix/data/local/PreferencesManager.kt`

View File

@@ -0,0 +1,19 @@
# UI/UX Reviewer Memory — Fluffytrix
See `patterns.md` for detailed design conventions.
## Key Conventions (short form)
- Spacing rhythm: 4/6/8/10/12/16/24/32dp — no odd values
- Channel list item padding: horizontal 12dp, vertical 10dp
- Settings screen row padding: vertical 6dp (info rows), vertical 12dp (nav rows)
- Icon size in lists: 20dp (channel icons, drag handles); 32dp (member avatars)
- Space sidebar icons: 48dp touch target, 64dp column width
- Color tokens: always MaterialTheme.colorScheme — EXCEPT senderColors array (Discord palette, intentional)
- Two hardcoded colors in SpaceList unread dots: `Color.Red` and `Color.Gray` — inconsistent with ChannelList which uses `colorScheme.error` and `colorScheme.primary`
- Typography: titleMedium for section headers in panels; labelMedium uppercase for channel sections; bodyLarge for channel names; bodyMedium for settings labels; bodySmall for subtitles
- Icon style: filled (Icons.Default.*) throughout — AutoMirrored used for directional icons
- Scaffold used in SettingsScreen with TopAppBar; MainScreen Scaffold has no topBar (custom layout)
- `collectAsStateWithLifecycle` used in MainScreen; `collectAsState` used in SettingsScreen — inconsistent
- MainScreen uses `@Immutable` data class for ProfileSheetState (good practice)
- Drag gesture threshold: 60f px for swipe-open/close channel list
- LazyListState for channel list is owned by ViewModel (correct — survives recomposition)

View File

@@ -0,0 +1,40 @@
# Fluffytrix UI Patterns
## Layout Structure
- MainScreen: Scaffold (no topBar) → Box → Row[SpaceList | MessageTimeline | MemberList?]
- Channel list overlays via AnimatedVisibility + zIndex(1f), slides from left
- SpaceList: 64dp wide, surfaceVariant background, icons 48dp
- ChannelList: fills width, surface background, max ~280dp visually
- MemberList: 240dp wide, surface background
- SettingsScreen: Scaffold + TopAppBar + Column + verticalScroll
## Spacing Rhythm
- Standard horizontal content padding: 16dp (settings, channel list header)
- Channel list items: horizontal 12dp, vertical 10dp
- Thread sub-items: start indent 24dp, horizontal 10dp, vertical 6dp
- Member list items: horizontal 8dp, vertical 4dp
- Settings rows: vertical 6dp (info/toggle), vertical 12dp (nav)
- Section dividers: padding(vertical = 16.dp)
## Color Token Usage
- Surface backgrounds: `colorScheme.surface` or `colorScheme.surfaceVariant`
- Selected item bg: `colorScheme.primaryContainer`; text: `colorScheme.onPrimaryContainer`
- Unread dot in ChannelList: error (mention) / primary (unread) — correct
- Unread dot in SpaceList: hardcoded `Color.Red` / `Color.Gray` — BUG, inconsistent
- Section text: `colorScheme.onSurface.copy(alpha = 0.7f)` uppercase labelMedium
- Disabled/subtle text: `colorScheme.onSurfaceVariant` or with `.copy(alpha = 0.6f)`
## Icon Conventions
- All icons: Icons.Default.* (filled style)
- Directional icons: Icons.AutoMirrored.Filled.*
- Icon size in list rows: 20dp
- All icons in touch targets: wrapped in IconButton (48dp auto-sizing) or clickable with min 48dp
## Notification (push/) — no Compose UI
- NotificationHelper: uses `R.mipmap.ic_launcher` as small icon (should be `R.drawable` monochrome)
- Channel: "messages" / IMPORTANCE_DEFAULT
- Tap → deep link via Intent extra "roomId" → DeepLinkState.set()
## State Collection
- MainScreen: collectAsStateWithLifecycle (correct, lifecycle-aware)
- SettingsScreen: collectAsState (should be collectAsStateWithLifecycle for consistency)