mirror of
https://github.com/cogwheel0/conduit.git
synced 2026-08-29 21:41:45 +00:00
* feat(notifications): in-app notification parity layer from socket events Derive notifications client-side from the existing Socket.IO streams, mirroring Open WebUI semantics (strict parity: chat:completion done-frame and new channel messages only). Surfaces as in-app banner (foreground) or OS notification (background), with a haptic cue, channel unread badges, and tap-to-deeplink. - Pure NotificationEventClassifier owns envelope semantics (16 tests) - NotificationRouter: gating predicate + LRU dedup, UI-free (14 tests) - Global socket listener reuses the ActiveChatsSync._bindSocket pattern - ActiveViewTracker reads existing active chat/channel providers - Settings page + 7 prefs (3 mirrored to server for cross-device parity) - Master toggle defaults off; permission requested on opt-in Master toggle off by default; voice-call service refactor deferred. * test(notifications): listener test + reset-on-visit unread clear - Add notification_socket_listener_test: wildcard handler registration (requireFocus:false), classify->route happy paths, non-notifiable filtering, reconnect->channels refresh, and re-bind on socket change. - ChannelsList.markRead clears the local unread badge on channel open (reset-on-visit), pairing with the existing server emitLastReadAt. Wired into ChannelPage._loadChannel; covered by new tests. * fix(notifications): address code review findings - Classifier suppresses channel messages with missing/empty author (malformed / system messages) instead of notifying with no title. - Mirror server notification prefs into local settings only once per server (first load / switch), so a fresh local toggle isn't clobbered by a settings reload that raced the write-through. - Wrap fire-and-forget notification ops (deep-link tap, cancelAll on sign-out) so failures log instead of becoming uncaught async errors. Note: CodeRabbit's 'missing break' critical is a false positive — this is a Dart 3 switch statement (no implicit fall-through); analyzer clean. * fix(notifications): address PR bot review (Macroscope/CodeRabbit) - Serialize LocalNotificationService.initialize() via a shared in-flight future so concurrent show()s don't run _plugin.initialize() in parallel. - Guard _onResponse against add-after-close: the plugin singleton keeps the tap callback registered after dispose(), which could throw on a late tap. - Drop unused areEnabled() (reported as returning a misleading iOS status; it had no callers). - Set the once-per-server notification-prefs flag only after the mirror succeeds, so a failed apply retries on a later reload. * fix(notifications): honor sound pref + harden OS notification posting Address second PR review cycle (Greptile/CodeRabbit): - Thread notificationSound into LocalNotificationService.show() so the OS notification respects the user's Sound toggle (iOS presentSound / Android playSound; Android 8+ channel-level sound noted as best-effort). - Use a monotonic notification id instead of dedupKey.hashCode to avoid 31-bit collisions silently replacing a notification in the drawer. - Contain plugin init failures inside _doInitialize (log + keep _initialized false for retry) so they can't bubble into routing. - Test: OS notification sound follows the notificationSound pref. * fix(notifications): await init before reading cold-launch tap Greptile P1: handleLaunchTap() called launchTap() -> getNotificationAppLaunchDetails() which returns null on Android until the plugin finishes native init, racing build()'s unawaited initialize() and silently dropping the deep link. Await initialize() (idempotent, shares the in-flight future) before querying the launch intent. * feat(notifications): surface settings in the iOS native profile sheet The iOS profile menu is rendered natively (data-driven via the native-sheet bridge), separate from the Flutter profile page. Add a Notifications row + toggle detail there so the feature isn't iOS-invisible: - NativeSheetRoutes.notificationSettings + menu row + loading detail - _hydrateNativeNotificationsDetail renders the 7 prefs as native toggles - main.dart handles each toggle's NativeSheetControlChanged, requests OS permission on master opt-in, and mirrors the 3 OWUI-aligned prefs to server * fix(notifications): skip channel notifications until current user resolves Macroscope: _currentUserId is '' while currentUserProvider is still loading, so the classifier's self-author filter (authorId == currentUserId) never matches and the user could be notified for their own channel messages. Skip channel classification when the id is unavailable. Adds a guard test. * fix(notifications): error-handling in native notifications hydration CodeRabbit: match the other hydration methods — wrap _hydrateNativeNotificationsDetail in try/catch and call _patchNativeDetailError so a failed _applyNativeDetail doesn't leave the native sheet stuck on the loading placeholder. * fix(notifications): render iOS native toggles as flat items The native notifications detail used sections:, but applyDetailPatch (the dynamic hydration path) only forwards items: — sections are dropped, so the sheet rendered empty. Flatten the 7 toggles into a single ordered items list like every other hydrated detail (about/account/voice). * fix(notifications): notify for the active chat when backgrounded Active-view suppression ran unconditionally, so a chat:completion for the chat the user just backgrounded (still the 'active' chat) was dropped and no OS notification fired — the reported bug. Gate active-view suppression on foreground only, mirroring Open WebUI's '(notViewingChat) || isInBackground'. Adds a test: backgrounded + active chat -> system notification. |
||
|---|---|---|
| .. | ||
| app_notification.dart | ||