client: Count thread replies toward the slow mode cooldown#6505
client: Count thread replies toward the slow mode cooldown#6505andremion wants to merge 2 commits into
Conversation
The composer cooldown is driven by ChannelState.lastSentMessageDate, which was derived only from the channel message list. Thread-only replies are excluded from that list, so sending a thread reply during slow mode did not start the countdown and the next send was rejected by the server. Track the current user's most recent thread-only reply at the channel-state write entry points and expose lastSentMessageDate as the later of the channel message date and that thread-reply date. The cooldown is now channel-wide and consistent between channel mode and thread mode, matching iOS. Applied to both ChannelStateImpl and the legacy ChannelStateLegacyImpl.
PR checklist ✅All required conditions are satisfied:
🎉 Great job! This PR is ready for review. |
|
@CodeRabbit review |
✅ Action performedReview finished.
|
Walkthrough
ChangesThread-only reply date tracking in channel state
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the 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.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateImpl.kt`:
- Around line 103-107: The `_lastSentThreadReplyDate` MutableStateFlow field
contributes to cooldown state tracking but is not being reset in the `destroy()`
method of the `ChannelStateImpl` class, which can leave stale cooldown
timestamps persisting across teardown and reuse cycles. Locate the `destroy()`
method and add a call to reset `_lastSentThreadReplyDate` to null, similar to
how other state fields are cleared during destruction, to ensure clean state
management when the channel is torn down or reused.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 5233e7f1-ed09-4787-80e0-afb5e90e7dcc
📒 Files selected for processing (5)
stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateImpl.ktstream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateLegacyImpl.ktstream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/LastSentMessageDate.ktstream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateImplLastSentMessageDateTest.ktstream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateLegacyImplTest.kt
SDK Size Comparison 📏
|
ChannelStateImpl.destroy() cleared every message-derived flow except the new _lastSentThreadReplyDate, so lastSentMessageDate would still emit a stale thread-reply date after teardown. Reset it alongside the message list.
|



Goal
When slow mode is active, sending a thread-only reply did not start the composer cooldown. The backend cooldown is channel-wide (per app, channel, user) and counts thread replies, so the user replied in a thread, saw no countdown, sent again, and the server rejected the message. iOS already counts thread replies; this brings Android (both Compose and XML) to the same behavior. Reported by the Flutter team.
Linear: AND-1230
Implementation
The composer cooldown is driven only by
ChannelState.lastSentMessageDate, whose only consumer is the cooldown observer in the sharedMessageComposerController(used by both the Compose and XML composers). That observer is not gated by message mode, so oncelastSentMessageDatereflects thread replies, the countdown works in both channel mode and thread mode with no composer change.lastSentMessageDatewas derived from the channel message list filtered to the current user. A thread-only reply (parentId != null && showInChannel == false) never contributed, for different reasons in the two channel-state implementations:ChannelStateImpl(default):shouldIgnoreUpsertiondrops thread-only replies at every write entry point, so they never enter the message list.ChannelStateLegacyImpl: they enter the raw map but are filtered out beforelastSentMessageDatereads the visible list.This change keeps the existing channel-message derivation untouched and adds a thread-reply contribution, then exposes
lastSentMessageDateas the later of the two. The contribution is a small per-channel value updated at the message write entry points with the current user's most recent thread-only reply date, and it only moves forward. The optimistic send path feeds it, so the countdown starts on send. The same change is applied to both state implementations, so behavior is consistent whetheruseLegacyChannelLogicisfalse(default) ortrue.The visible message list,
shouldIgnoreUpsertion, andlastMessageAtare unchanged, so no other consumer is affected. There is no public API change.Cross-session resume needs no extra work: on channel init the offline load reads messages (including thread replies) and feeds the same value, which only moves forward, so it survives the later server refresh.
Parity note: this matches iOS, where the cooldown comes from
channel.lastMessageFromCurrentUser(which counts thread replies) viacurrentCooldownTime(), used by both the UIKit and SwiftUI composers.Testing
Manual steps (the Compose and XML sample apps behave the same):
Automated: unit tests in
ChannelStateImplLastSentMessageDateTestand the additions toChannelStateLegacyImplTestcover a thread reply updatinglastSentMessageDate(and not updating it for another user's reply), in both state implementations, plus the channel-wide and cross-refresh cases.Summary by CodeRabbit
Bug Fixes
Tests