-
Notifications
You must be signed in to change notification settings - Fork 374
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
Conversation
WalkthroughAdds support for channel-level pending messages: new Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant App
participant ChannelClientState as ChannelClientState
participant ChannelState as ChannelState
App->>ChannelClientState: updateChannelState(updatedState)
note right of ChannelClientState #d3f9d8: updatedState may include pendingMessages
ChannelClientState->>ChannelState: copyWith(pendingMessages: updatedState.pendingMessages, ...)
ChannelState-->>ChannelClientState: new ChannelState (with pendingMessages)
ChannelClientState-->>App: notify state updated / pendingMessagesStream emits
sequenceDiagram
autonumber
participant Server
participant ChannelConfig as ChannelConfig
Server-->>ChannelConfig: JSON { "mark_messages_pending": true/false }
ChannelConfig-->>Server: toJson() includes "mark_messages_pending"
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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.dartpackages/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_messagesJSON field and maps it to the Message objects. The generated code properly uses the custom_pendingMessagesReadValuehelper function for reading the data.
58-59: LGTM: Proper JSON serialization for pending messagesThe toJson implementation correctly serializes the pendingMessages list to the
pending_messagesfield, 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_pendingwith 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_pendingJSON 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
pendingMessagesfrom 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
_pendingMessagesReadValuehelper correctly handles the nested JSON structure where pending messages are wrapped in objects containing amessagefield. 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
@JsonKeyannotation correctly uses the custom read function to handle the nested JSON structure from the Stream Chat API.
107-107: LGTM: Proper copyWith implementationThe
pendingMessagesparameter 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.
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.
2e327d8 to
10774a1
Compare
This commit adds `pendingMessages` and `pendingMessagesStream` to the `Channel` class, providing access to the list of pending messages both directly and as a stream.
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
🧹 Nitpick comments (1)
packages/stream_chat/lib/src/core/models/channel_state.dart (1)
80-85: Consider allowing explicit clearing via copyWith sentinelcopyWith uses pendingMessages ?? this.pendingMessages, which prevents setting it to null intentionally. If you need that control (similar to draft), switch to a sentinel:
- ChannelState copyWith({ + ChannelState copyWith({ ChannelModel? channel, List<Message>? messages, List<Member>? members, List<Message>? pinnedMessages, int? watcherCount, List<User>? watchers, List<Read>? read, Member? membership, - Object? draft = _nullConst, - List<Message>? pendingMessages, + Object? draft = _nullConst, + Object? pendingMessages = _nullConst, ChannelPushPreference? pushPreferences, }) => ChannelState( channel: channel ?? this.channel, messages: messages ?? this.messages, members: members ?? this.members, pinnedMessages: pinnedMessages ?? this.pinnedMessages, watcherCount: watcherCount ?? this.watcherCount, watchers: watchers ?? this.watchers, read: read ?? this.read, membership: membership ?? this.membership, draft: draft == _nullConst ? this.draft : draft as Draft?, - pendingMessages: pendingMessages ?? this.pendingMessages, + pendingMessages: pendingMessages == _nullConst + ? this.pendingMessages + : pendingMessages as List<Message>?, pushPreferences: pushPreferences ?? this.pushPreferences, );Optional, but it keeps API consistent with draft handling.
Also applies to: 96-122
📜 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 (5)
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)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/stream_chat/lib/src/core/models/channel_state.g.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). (3)
- GitHub Check: build (android)
- GitHub Check: test
- GitHub Check: build (ios)
🔇 Additional comments (3)
packages/stream_chat/lib/src/client/channel.dart (1)
3379-3381: Propagation looks good; ensure clearing semantics are handled upstreamForwarding updatedState.pendingMessages into copyWith is correct. Note: if fromJson yields null for an empty server payload, copyWith will retain stale pendingMessages. Make sure ChannelState.readValue returns an empty list ([]) when the server sends an empty pending_messages array so state clears as expected.
packages/stream_chat/lib/src/core/models/channel_config.g.dart (1)
37-38: LGTM (generated mapping matches the new field)fromJson/toJson correctly wire mark_messages_pending ⇄ markMessagesPending with default false. No action needed.
Also applies to: 61-62
packages/stream_chat/lib/src/core/models/channel_config.dart (1)
29-30: Field addition aligns with featuremarkMessagesPending with default false and docs look good. Ensure this default matches the server’s default behavior.
Also applies to: 95-97
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
🧹 Nitpick comments (1)
packages/stream_chat/CHANGELOG.md (1)
11-12: Call out the new ChannelConfig flag.The SDK now exposes
ChannelConfig.markMessagesPending; it’s worth mentioning here so integrators know how to enable pending messages at the config level.
📜 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 (2)
packages/stream_chat/CHANGELOG.md(1 hunks)packages/stream_chat/test/fixtures/channel_state_to_json.json(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- packages/stream_chat/test/fixtures/channel_state_to_json.json
⏰ 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). (7)
- GitHub Check: build (ios)
- GitHub Check: build (android)
- GitHub Check: test
- GitHub Check: stream_chat_localizations
- GitHub Check: stream_chat_flutter
- GitHub Check: stream_chat_persistence
- GitHub Check: stream_chat_flutter_core
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #2400 +/- ##
==========================================
- Coverage 63.94% 63.91% -0.03%
==========================================
Files 413 413
Lines 25898 25914 +16
==========================================
+ Hits 16561 16564 +3
- Misses 9337 9350 +13 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Submit a pull request
Fixes: 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:
markMessagesPendingfield toChannelConfigto enable/disablepending messages for a channel.
pendingMessagesfield toChannelStateto store the list ofpending messages for a channel.
Channelto includependingMessagesin the channel state.Summary by CodeRabbit