Skip to content

Update message grouping to take time difference into account#6328

Open
gpunto wants to merge 5 commits intov7from
time-based-message-grouping
Open

Update message grouping to take time difference into account#6328
gpunto wants to merge 5 commits intov7from
time-based-message-grouping

Conversation

@gpunto
Copy link
Copy Markdown
Contributor

@gpunto gpunto commented Apr 1, 2026

Goal

Messages from the same user that are far apart in time should not be visually grouped together. The
default MessagePositionHandler now breaks groups when messages are more than 60 seconds apart.

Implementation

  • Update MessagePositionHandler.defaultHandler to check the time difference between consecutive
    messages when determining grouping
  • Messages from the same user that are more than 60 seconds apart are no longer grouped together

Testing

Open a conversation where the same user sent messages with large time gaps and verify they are no
longer grouped

Summary by CodeRabbit

  • Bug Fixes
    • Updated message footer visibility behavior to use "last in group" display as default instead of time-based calculation
    • Enhanced message grouping to enforce a 60-second time window between consecutive messages from the same user
    • Simplified edited message footer display

@gpunto gpunto added the pr:improvement Improvement label Apr 1, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

PR checklist ✅

All required conditions are satisfied:

  • Title length is OK (or ignored by label).
  • At least one pr: label exists.
  • Sections ### Goal, ### Implementation, and ### Testing are filled.

🎉 Great job! This PR is ready for review.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.25 MB 5.86 MB 0.61 MB 🔴
stream-chat-android-ui-components 10.60 MB 11.17 MB 0.57 MB 🔴
stream-chat-android-compose 12.81 MB 12.41 MB -0.40 MB 🚀

@gpunto gpunto force-pushed the time-based-message-grouping branch from 3b41166 to 67cb599 Compare April 2, 2026 14:01
@gpunto
Copy link
Copy Markdown
Contributor Author

gpunto commented Apr 2, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 2, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 2, 2026

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 2, 2026

Walkthrough

This pull request removes the MessageFooterVisibility.WithTimeDifference feature entirely, replaces it with position-based footer visibility, and introduces time-based message grouping constraints to the default MessagePositionHandler. Related factory defaults are updated to use LastInGroup instead, and the footer rendering composable is simplified by removing internal state management.

Changes

Cohort / File(s) Summary
Footer Visibility Removal
stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/messages/list/MessageFooterVisibility.kt, stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/utils/extensions/MessageFooterVisibility.kt, stream-chat-android-ui-common/api/stream-chat-android-ui-common.api
Removed WithTimeDifference sealed subclass from MessageFooterVisibility, deleted associated time-difference-based logic from the footer visibility extension, and removed the class from the public API surface.
Factory Default Updates
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/messages/MessagesViewModelFactory.kt, stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessageListController.kt, stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/messages/MessageListViewModelFactory.kt
Changed default messageFooterVisibility parameter from MessageFooterVisibility.WithTimeDifference() to MessageFooterVisibility.LastInGroup across factory classes and controllers.
Message Grouping Constraints
stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessagePositionHandler.kt
Extended defaultHandler() with maxGroupTimeMillis parameter (default 60s) to enforce time-based grouping constraints in addition to existing user/system message checks.
Footer UI Simplification
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/MessageFooter.kt
Removed showEditInfo state management and internal MessageEditedTimestamp composable; refactored footer rendering to compute showEditLabel and timestampStyle unconditionally with simplified conditional branches.
Test Updates
stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessageListControllerTests.kt, stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessagePositionHandlerTest.kt, stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/utils/extensions/MessageFooterVisibilityTest.kt
Updated test helpers with deterministic timestamp generation; added three new test cases for time-gap exceeding 60 seconds; removed entire WithTimeDifference test suite and adjusted LastInGroup test assertion.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Time-based footers hop away,
Position logic takes the day,
Grouping messages tight and close,
Within a minute's gentle dose,
Simpler code—the best bouquet! 🌱✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: introducing time-based constraints to message grouping logic.
Description check ✅ Passed The description covers the goal, implementation, and testing approach, but lacks UI changes section and comprehensive testing details required by the template.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch time-based-message-grouping

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a 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 (2)
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/MessageFooter.kt (1)

80-137: Add snapshot coverage for the edited-label-only branch.

The new !showMessageFooter && showEditLabel path changes visible layout and spacing, but there isn't matching Compose regression coverage in this PR. A Paparazzi case for an edited message in the middle of a group would lock this down.

Based on learnings: "Applies to /stream-chat-android-compose//*Test.kt : Add Paparazzi snapshots for Compose UI regressions and run verifyPaparazziDebug."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/MessageFooter.kt`
around lines 80 - 137, Add a Paparazzi snapshot test that exercises the new
branch where messageItem.showMessageFooter is false and showEditLabel is true:
create a test in the Compose UI test suite (matching other tests under
/stream-chat-android-compose/**/*Test.kt) which composes the MessageFooter (or
the top-level composable that renders this footer) with a message whose isMine
flag and/or grouping produce showMessageFooter = false and showEditLabel = true,
positioned as a middle-of-group message to match spacing; capture and verify the
snapshot (run verifyPaparazziDebug) so the edited-label-only layout and spacing
are covered and will catch regressions.
stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessagePositionHandlerTest.kt (1)

179-231: Add the equality boundary case at 60_000 ms.

The new rule is “more than 60 seconds apart”, but these tests only cover > 60s. An exact 60_000L case would catch a future >= regression.

🧪 Suggested test case
+    `@Test`
+    fun `Message should stay grouped when time difference is exactly 60 seconds`() {
+        val previousMessage = createMessage(user1, timeOffset = 0L)
+        val currentMessage = createMessage(user1, timeOffset = 60_000L)
+        val nextMessage = createMessage(user1, timeOffset = 120_000L)
+
+        val result = defaultHandler.handleMessagePosition(
+            previousMessage,
+            currentMessage,
+            nextMessage,
+            isAfterDateSeparator = false,
+            isBeforeDateSeparator = false,
+            isInThread = false,
+        )
+
+        result `should be equal to` MessagePosition.MIDDLE
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessagePositionHandlerTest.kt`
around lines 179 - 231, Add tests that cover the exact 60_000 ms boundary by
mirroring the three existing cases but using timeOffset differences equal to
60_000L (e.g., previous=0L, current=60_000L, next=120_000L for the NONE-like
scenario; previous=0L, current=60_000L, next=121_000L for the TOP-like scenario;
previous=0L, current=1_000L, next=61_000L for the BOTTOM-like scenario) and
assert the expected MessagePosition via defaultHandler.handleMessagePosition
(keep isAfterDateSeparator/isBeforeDateSeparator/isInThread the same and use
createMessage and MessagePosition symbols to locate the code). Ensure these
tests will fail if the comparison becomes >= 60_000 instead of > 60_000.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessagePositionHandler.kt`:
- Around line 19-28: The file-level import of
MessagePositionHandler.Companion.DEFAULT_MAX_GROUP_TIME_MILLIS is invalid
because DEFAULT_MAX_GROUP_TIME_MILLIS is declared private in the companion;
remove that import and reference DEFAULT_MAX_GROUP_TIME_MILLIS directly where
needed within MessagePositionHandler (or make the companion constant
internal/public if you intend to keep the import). Locate
DEFAULT_MAX_GROUP_TIME_MILLIS in the MessagePositionHandler companion and either
delete the file-scope import line for
MessagePositionHandler.Companion.DEFAULT_MAX_GROUP_TIME_MILLIS or change the
constant's visibility to internal/public so the import becomes valid.

---

Nitpick comments:
In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/MessageFooter.kt`:
- Around line 80-137: Add a Paparazzi snapshot test that exercises the new
branch where messageItem.showMessageFooter is false and showEditLabel is true:
create a test in the Compose UI test suite (matching other tests under
/stream-chat-android-compose/**/*Test.kt) which composes the MessageFooter (or
the top-level composable that renders this footer) with a message whose isMine
flag and/or grouping produce showMessageFooter = false and showEditLabel = true,
positioned as a middle-of-group message to match spacing; capture and verify the
snapshot (run verifyPaparazziDebug) so the edited-label-only layout and spacing
are covered and will catch regressions.

In
`@stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessagePositionHandlerTest.kt`:
- Around line 179-231: Add tests that cover the exact 60_000 ms boundary by
mirroring the three existing cases but using timeOffset differences equal to
60_000L (e.g., previous=0L, current=60_000L, next=120_000L for the NONE-like
scenario; previous=0L, current=60_000L, next=121_000L for the TOP-like scenario;
previous=0L, current=1_000L, next=61_000L for the BOTTOM-like scenario) and
assert the expected MessagePosition via defaultHandler.handleMessagePosition
(keep isAfterDateSeparator/isBeforeDateSeparator/isInThread the same and use
createMessage and MessagePosition symbols to locate the code). Ensure these
tests will fail if the comparison becomes >= 60_000 instead of > 60_000.
🪄 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: 92769122-b1ef-4762-8ff7-7e91f50af391

📥 Commits

Reviewing files that changed from the base of the PR and between f88eb83 and 67cb599.

📒 Files selected for processing (11)
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/MessageFooter.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/messages/MessagesViewModelFactory.kt
  • stream-chat-android-ui-common/api/stream-chat-android-ui-common.api
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessageListController.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessagePositionHandler.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/messages/list/MessageFooterVisibility.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/utils/extensions/MessageFooterVisibility.kt
  • stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessageListControllerTests.kt
  • stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessagePositionHandlerTest.kt
  • stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/utils/extensions/MessageFooterVisibilityTest.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/messages/MessageListViewModelFactory.kt
💤 Files with no reviewable changes (3)
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/utils/extensions/MessageFooterVisibility.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/messages/list/MessageFooterVisibility.kt
  • stream-chat-android-ui-common/api/stream-chat-android-ui-common.api

* @param formatType The type of formatting to provide. By default, it's [DateFormatType.RELATIVE].
*/
@Composable
internal fun MessageEditedTimestamp(
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Showing the edited timestamp is a feature that doesn't exist anymore in the new designs, so I'm also removing that

MessageEditedTimestamp(message = message)
}
} else if (showEditLabel) {
Row(
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Edited" should be shown even when the rest of the footer is not shown

*
* @param timeDifferenceMillis Time duration after which we show the message footer.
*/
public data class WithTimeDifference(
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in Slack, this now conflicted with the logic in MessagePositionHandler so I removed it. Note: we still have the option to remove the whole MessageFooterVisibility before releasing v7. It seems kind of an outdated concept now that:

  • MessagePositionHandler handles time-based grouping
  • the footer can be replaced through the component factory

@gpunto gpunto marked this pull request as ready for review April 2, 2026 14:40
@gpunto gpunto requested a review from a team as a code owner April 2, 2026 14:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:improvement Improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant