-
Notifications
You must be signed in to change notification settings - Fork 368
feat(llc): add pending messages for channels #2400
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
This commit introduces the concept of pending messages for channels. Pending messages are messages that are awaiting moderation before being visible to all users in a channel. The following changes were made: - Added `markMessagesPending` field to `ChannelConfig` to enable/disable pending messages for a channel. - Added `pendingMessages` field to `ChannelState` to store the list of pending messages for a channel. - Updated `MessageListView` to mark messages as read only if pending messages are disabled for the channel. - Updated `Channel` to include `pendingMessages` in the channel state.
The `pending_messages` field in the channel state was not being parsed correctly. This change ensures that the field is parsed as a list of maps, and that the `message` field is extracted from each map.
WalkthroughPropagates pending messages support: adds ChannelConfig.markMessagesPending, ChannelState.pendingMessages with JSON handling, includes pendingMessages in ChannelClientState.updateChannelState, and encapsulates read-marking checks in MessageListView via _maybeMarkMessagesAsRead. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant UI as MessageListView
participant Channel as Channel
participant Config as ChannelConfig
participant Server as API
User->>UI: Scrolls to bottom / message visible
UI->>Channel: Query unread count & read events allowed
UI->>Config: Check markMessagesPending flag
alt Guards pass (unread>0, readEvents allowed, pending enabled, at-bottom)
UI->>Server: markRead()
Server-->>Channel: Sends updated ChannelState (includes pending_messages)
Channel->>Channel: updateChannelState(updatedState) — includes pendingMessages
else Guards fail
UI-->>User: No read action taken
end
sequenceDiagram
autonumber
participant ChannelClient as ChannelClientState
participant Updated as UpdatedState
participant Local as ChannelState
ChannelClient->>Updated: Receives updated channel state from server
ChannelClient->>Local: Rebuild ChannelState with fields:
Note right of Local: messages, watchers, read, draft,\npinnedMessages, pushPreferences, pendingMessages
Local-->>ChannelClient: New ChannelState applied
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (5 passed)
✨ Finishing touches🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
packages/stream_chat/lib/src/client/channel.dart
(1 hunks)packages/stream_chat/lib/src/core/models/channel_config.dart
(2 hunks)packages/stream_chat/lib/src/core/models/channel_config.g.dart
(2 hunks)packages/stream_chat/lib/src/core/models/channel_state.dart
(4 hunks)packages/stream_chat/lib/src/core/models/channel_state.g.dart
(2 hunks)packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart
(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-25T08:19:01.469Z
Learnt from: xsahil03x
PR: GetStream/stream-chat-flutter#2394
File: packages/stream_chat_flutter/lib/src/message_action/message_actions_builder.dart:82-92
Timestamp: 2025-09-25T08:19:01.469Z
Learning: In the Stream Chat Flutter library, when deleting a message with MessageSendingStatus.failed or MessageSendingStatus.failed_update status, the _deleteMessage method in channel.dart automatically handles deletion locally via _deleteLocalMessage without making API calls, preventing 404 errors and deletingFailed states.
Applied to files:
packages/stream_chat/lib/src/client/channel.dart
packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart
📚 Learning: 2025-09-25T08:19:01.469Z
Learnt from: xsahil03x
PR: GetStream/stream-chat-flutter#2394
File: packages/stream_chat_flutter/lib/src/message_action/message_actions_builder.dart:82-92
Timestamp: 2025-09-25T08:19:01.469Z
Learning: In the Stream Chat Flutter library, when deleting a message that hasn't been sent to the server yet (message.remoteCreatedAt == null) or is bounced with error, the _deleteMessage method in channel.dart automatically handles deletion locally via _deleteLocalMessage without making API calls, preventing 404 errors and deletingFailed states.
Applied to files:
packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: build (android)
- GitHub Check: build (ios)
- GitHub Check: test
- GitHub Check: analyze
- GitHub Check: stream_chat_localizations
- GitHub Check: stream_chat_flutter_core
- GitHub Check: stream_chat_flutter
- GitHub Check: stream_chat_persistence
- GitHub Check: stream_chat
🔇 Additional comments (11)
packages/stream_chat/lib/src/core/models/channel_state.g.dart (2)
35-39
: LGTM: Clean JSON serialization for pending messagesThe implementation correctly handles the deserialization of pending messages from the
pending_messages
JSON field and maps it to the Message objects. The generated code properly uses the custom_pendingMessagesReadValue
helper function for reading the data.
58-59
: LGTM: Proper JSON serialization for pending messagesThe toJson implementation correctly serializes the pendingMessages list to the
pending_messages
field, maintaining consistency with the Stream Chat API structure.packages/stream_chat/lib/src/core/models/channel_config.g.dart (2)
37-37
: LGTM: Correct JSON deserialization with appropriate defaultThe field is properly deserialized from
mark_messages_pending
with a sensible default value offalse
, which aligns with the Stream Chat API documentation where this feature needs to be explicitly enabled.
61-61
: LGTM: Consistent JSON serializationThe field is correctly serialized back to the
mark_messages_pending
JSON key, maintaining API compatibility.packages/stream_chat/lib/src/client/channel.dart (1)
3343-3343
: LGTM: Proper state synchronization for pending messagesThe change correctly propagates
pendingMessages
from the updated channel state into the local channel state duringupdateChannelState
. This ensures that pending messages are properly synchronized between server and local state.packages/stream_chat/lib/src/core/models/channel_config.dart (2)
29-29
: LGTM: Consistent constructor parameterThe new parameter is correctly added to the constructor with an appropriate default value of
false
, ensuring backward compatibility.
95-96
: LGTM: Well-documented new fieldThe documentation clearly explains the purpose of the field and its relationship to pending messages functionality. The field declaration is properly typed as a final boolean.
packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart (1)
1481-1482
: LGTM: Cleaner async handlingGood refactoring to extract the message read logic into a separate method and handle it asynchronously with proper error suppression using
.ignore()
.packages/stream_chat/lib/src/core/models/channel_state.dart (3)
64-78
: LGTM: Robust JSON parsing for pending messages structureThe
_pendingMessagesReadValue
helper correctly handles the nested JSON structure where pending messages are wrapped in objects containing amessage
field. The implementation properly validates the data types and handles edge cases like null values and empty arrays.
80-84
: LGTM: Well-documented field with proper JSON annotationThe field is well-documented with clear explanation of its purpose and visibility constraints. The
@JsonKey
annotation correctly uses the custom read function to handle the nested JSON structure from the Stream Chat API.
107-107
: LGTM: Proper copyWith implementationThe
pendingMessages
parameter is correctly added to the copyWith method and properly propagated to the new instance.Also applies to: 120-120
packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart (2)
873-878
: Manual dismiss bypasses pending-messages guardUnreadIndicatorButton uses _markMessagesAsRead directly, which ignores the new pending-messages guard. Route it through the guard (manual path) so pending channels aren’t marked read accidentally.
Apply:
- onDismissTap: _markMessagesAsRead, + onDismissTap: () => _maybeMarkMessagesAsRead(manual: true),
957-971
: Debounced callbacks capture a stale channel instancedebouncedMarkRead/debouncedMarkThreadRead are created from method tear-offs bound to the channel at creation time. If the StreamChannel changes (handled above), these closures will still call markRead/markThreadRead on the old channel. Make them reference streamChannel at call time to avoid stale bindings.
Apply:
- late final debouncedMarkRead = switch (streamChannel) { - final streamChannel? => debounce( - streamChannel.channel.markRead, - const Duration(seconds: 1), - ), - _ => null, - }; + late final debouncedMarkRead = debounce( + () { + final ch = streamChannel?.channel; + if (ch == null) return; + ch.markRead(); + }, + const Duration(seconds: 1), + ); - late final debouncedMarkThreadRead = switch (streamChannel) { - final streamChannel? => debounce( - streamChannel.channel.markThreadRead, - const Duration(seconds: 1), - ), - _ => null, - }; + late final debouncedMarkThreadRead = debounce( + (List<String> parentIds) { + final ch = streamChannel?.channel; + if (ch == null) return; + ch.markThreadRead(parentIds); + }, + const Duration(seconds: 1), + );
🧹 Nitpick comments (1)
packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart (1)
1486-1517
: Don’t block thread read events; add a “manual” path and tighten guardsTwo gaps:
- In thread view, gating on channel unreadCount can skip needed markThreadRead calls.
- Reuse this guard for manual actions (e.g., unread-indicator dismiss) to consistently honor pending-messages configs.
Proposed update:
- Add parameter {bool manual = false}.
- Skip hasUnread and bottom checks when manual.
- Skip hasUnread when in a thread view.
Apply:
- Future<void> _maybeMarkMessagesAsRead() async { + Future<void> _maybeMarkMessagesAsRead({bool manual = false}) async { final channel = streamChannel?.channel; if (channel == null) return; - final hasUnread = (channel.state?.unreadCount ?? 0) > 0; - if (!hasUnread) return; + // For manual calls or thread views, don't require channel-level unread count. + if (!manual && !_isThreadConversation) { + final hasUnread = (channel.state?.unreadCount ?? 0) > 0; + if (!hasUnread) return; + } final allowMarkRead = channel.config?.readEvents == true; if (!allowMarkRead) return; // When markMessagesPending is enabled, messages are held for moderation // and should not be immediately marked as read. final markPendingEnabled = channel.config?.markMessagesPending == true; if (markPendingEnabled) return; - final canMarkReadAtBottom = widget.markReadWhenAtTheBottom; - if (!canMarkReadAtBottom) return; + // Only auto-mark at bottom when not invoked manually. + if (!manual) { + final canMarkReadAtBottom = widget.markReadWhenAtTheBottom; + if (!canMarkReadAtBottom) return; + } // Mark messages as read if it's allowed. return _markMessagesAsRead(); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart
(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-25T08:19:01.469Z
Learnt from: xsahil03x
PR: GetStream/stream-chat-flutter#2394
File: packages/stream_chat_flutter/lib/src/message_action/message_actions_builder.dart:82-92
Timestamp: 2025-09-25T08:19:01.469Z
Learning: In the Stream Chat Flutter library, when deleting a message that hasn't been sent to the server yet (message.remoteCreatedAt == null) or is bounced with error, the _deleteMessage method in channel.dart automatically handles deletion locally via _deleteLocalMessage without making API calls, preventing 404 errors and deletingFailed states.
Applied to files:
packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart
📚 Learning: 2025-09-25T08:19:01.469Z
Learnt from: xsahil03x
PR: GetStream/stream-chat-flutter#2394
File: packages/stream_chat_flutter/lib/src/message_action/message_actions_builder.dart:82-92
Timestamp: 2025-09-25T08:19:01.469Z
Learning: In the Stream Chat Flutter library, when deleting a message with MessageSendingStatus.failed or MessageSendingStatus.failed_update status, the _deleteMessage method in channel.dart automatically handles deletion locally via _deleteLocalMessage without making API calls, preventing 404 errors and deletingFailed states.
Applied to files:
packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: stream_chat_localizations
- GitHub Check: stream_chat_persistence
- GitHub Check: stream_chat_flutter_core
- GitHub Check: stream_chat_flutter
- GitHub Check: build (android)
- GitHub Check: test
- GitHub Check: build (ios)
- GitHub Check: analyze
🔇 Additional comments (1)
packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart (1)
1481-1483
: Good call site: centralized mark‑read triggerCalling the new guard here keeps the scroll handler lean. Looks good.
Submit a pull request
Linear: FLU-231
Description of the pull request
This commit introduces the concept of pending messages for channels.
Pending messages are messages that are awaiting moderation before being
visible to all users in a channel.
The following changes were made:
markMessagesPending
field toChannelConfig
to enable/disablepending messages for a channel.
pendingMessages
field toChannelState
to store the list ofpending messages for a channel.
MessageListView
to mark messages as read only if pending messagesare disabled for the channel.
Channel
to includependingMessages
in the channel state.Summary by CodeRabbit
New Features
Bug Fixes