Skip to content

Comments

Update user reactions sheet design#6168

Merged
gpunto merged 7 commits intov7from
redesign/user-reactions-sheet
Feb 20, 2026
Merged

Update user reactions sheet design#6168
gpunto merged 7 commits intov7from
redesign/user-reactions-sheet

Conversation

@gpunto
Copy link
Contributor

@gpunto gpunto commented Feb 19, 2026

Goal

Update design of the sheet with a message's reaction details, i.e. what reactions are there and who reacted.

Implementation

  • Most of it is just implementing new components
  • Moved to ModalBottomSheet to provide the expected experience (draggable sheet)
  • Changed factory functions to ReactionsMenu + ReactionsMenuContent so the menu container & content can be customized independently
  • Removed messageOptionsUserReactionAlignment

🎨 UI Changes

Before After
Screenshot_20260219_101816 Screenshot_20260219_103749

Testing

Can be checked in the sample

Summary by CodeRabbit

  • New Features

    • Introduced reaction chips display with scrollable row of emoji reactions and count indicators.
    • Added dedicated user reaction rows showing who reacted with better visual organization.
  • UI/UX Changes

    • Redesigned reactions menu interface for improved usability.
    • Removed animations from reactions and message menus for streamlined interactions.
    • Updated reaction-related icons and text labels.
  • Refactoring

    • Restructured color theme system with improved legacy color management.

@gpunto gpunto added the pr:improvement Improvement label Feb 19, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Feb 19, 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
Contributor

github-actions bot commented Feb 19, 2026

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.25 MB 5.69 MB 0.44 MB 🟡
stream-chat-android-ui-components 10.60 MB 10.95 MB 0.36 MB 🟡
stream-chat-android-compose 12.81 MB 11.92 MB -0.89 MB 🚀

@gpunto gpunto marked this pull request as ready for review February 19, 2026 10:24
@gpunto gpunto requested a review from a team as a code owner February 19, 2026 10:24
@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

Walkthrough

A refactoring of the reactions UI in Stream Chat Android Compose, transitioning from column/grid-based layouts to horizontal row-based layouts, simplifying the SelectedReactionsMenu API surface, and restructuring theme colors with the introduction of StreamLegacyColors.

Changes

Cohort / File(s) Summary
Reactions Row Components
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/selectedmessage/ReactionCountRow.kt, UserReactionRow.kt
New horizontal, scrollable row components for displaying reaction chips and user reactions. ReactionCountRow renders emoji chips with optional add-reaction button; UserReactionRow displays individual user reactions in a horizontal layout with avatar, name, and reaction icon.
Reactions Menu Refactoring
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/selectedmessage/SelectedReactionsMenu.kt
Introduced new ReactionsMenuContent composable that consolidates reactions UI logic. Simplified SelectedReactionsMenu parameters (removed shape, overlayColor, showMoreReactionsIcon, headerContent, centerContent); now wraps content in ModalBottomSheet. Added buildReactionGroups helper and updated preview data usage.
Removed Reactions Components
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/userreactions/UserReactionItem.kt, UserReactions.kt
Deleted vertical grid/column-based reaction display components. Functionality consolidated into new row-based UserReactionRow and ReactionCountRow components.
Theme Colors Restructuring
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/StreamColors.kt
Introduced StreamLegacyColors class to organize legacy color properties. Refactored StreamColors to delegate legacy colors to StreamLegacyColors instead of storing directly; added new chipText color property. Updated constructor and copy methods accordingly.
ChatTheme Updates
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatTheme.kt
Removed messageOptionsUserReactionAlignment parameter and LocalMessageOptionsUserReactionAlignment composition local. Added getLocalMessageComposerFloatingStyleEnabled provider to support expanded theming options.
Component Factory and MessagesScreen
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt, ui/messages/MessagesScreen.kt
Renamed ReactionsMenuHeaderContent factory to ReactionsMenuContent with updated parameters (added currentUser, ownCapabilities; removed showMoreReactionsIcon). Removed ReactionsMenuCenterContent factory. Updated MessagesScreen to use new parameter name (onShowMoreReactionsSelected) and removed AnimatedVisibility animations from menu rendering.
Resources and Preview Data
stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_reaction_add.xml, values/strings.xml, stream-chat-android-previewdata/src/main/kotlin/io/getstream/chat/android/previewdata/PreviewReactionData.kt, PreviewUserReactionData.kt
Added new add-reaction icon drawable. Updated reaction string resources (changed "Message Reaction(s)" to "Reaction(s)"; added new reaction action strings). Extended preview data with oneReactionGroup and manyReactionGroups; updated PreviewUserReactionData to include isMine flag.
Sample and Documentation
stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/MessagesActivity.kt, stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/messages/SelectedReactionsMenu.kt, stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/SelectedReactionsMenuTest.kt
Updated sample activity to remove shape parameter from SelectedReactionsMenu. Updated documentation example to remove shape and headerContent customization. Updated test to use ReactionsMenuContent with reactionGroups data.
API Surface Changes
stream-chat-android-compose/api/stream-chat-android-compose.api
Added new ComposableSingletons for ReactionCountRowKt and UserReactionRowKt. Expanded SelectedReactionsMenuKt with lambda-3 field and new function signatures. Introduced StreamLegacyColors class with comprehensive color getters and copy methods. Updated StreamColors signatures and removed several component methods.

Sequence Diagram

sequenceDiagram
    participant User
    participant MessagesScreen
    participant SelectedReactionsMenu
    participant ReactionsMenuContent
    participant ReactionCountRow
    participant UserReactionRow
    participant Message
    participant ChatTheme

    User->>MessagesScreen: Tap message reactions
    MessagesScreen->>SelectedReactionsMenu: Show reactions in ModalBottomSheet
    SelectedReactionsMenu->>ReactionsMenuContent: Initialize with message
    ReactionsMenuContent->>Message: Extract reactionGroups & reactions
    Message-->>ReactionsMenuContent: Return grouped reactions & user reactions
    ReactionsMenuContent->>ChatTheme: Get theme colors & styling
    ChatTheme-->>ReactionsMenuContent: Return color palette & theme tokens
    ReactionsMenuContent->>ReactionCountRow: Render reaction chips with counts
    ReactionsMenuContent->>UserReactionRow: Render individual user reactions
    User->>ReactionCountRow: Tap reaction chip or add button
    ReactionCountRow->>SelectedReactionsMenu: Trigger onReactionOptionSelected or onAddReactionClick
    User->>UserReactionRow: Tap user reaction (if own reaction)
    UserReactionRow->>SelectedReactionsMenu: Trigger remove reaction callback
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

pr:breaking-change

Suggested reviewers

  • andremion

Poem

🐰 Reactions hop in rows so bright,
Old grids retire, new layouts delight,
Colors organized, themes aligned right,
Simplifying menus, removing the blight,
Stream's compose hops into the light! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.62% 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 clearly describes the main change: updating the user reactions sheet design, which aligns with the primary objective of redesigning the reactions UI component.
Description check ✅ Passed The description includes Goal, Implementation, UI Changes with before/after screenshots, and Testing sections, covering most template requirements. However, the contributor and reviewer checklists are not completed.

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

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch redesign/user-reactions-sheet

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

@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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/StreamColors.kt (1)

25-114: ⚠️ Potential issue | 🟡 Minor

Add KDoc entry for chipText.

chipText is a public color but isn’t documented in the class KDoc.

📝 Suggested change
- * `@param` chatWaveformBarPlaying Used for play control button icon.
+ * `@param` chatWaveformBarPlaying Used for play control button icon.
+ * `@param` chipText Used for reaction chip text and count color.
  * `@param` controlPlayControlBg Used for play control button background.

As per coding guidelines Document public APIs with KDoc, including thread expectations and state notes.

🤖 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/theme/StreamColors.kt`
around lines 25 - 114, The class KDoc is missing a parameter description for the
public color chipText; update the StreamColors KDoc to add a `@param` entry named
chipText explaining its purpose (e.g., color used for text inside chips/pills),
typical usage context, and any state/thread expectations, so the public API is
fully documented; locate the KDoc block at the top of StreamColors.kt where
other `@param` tags are listed and insert the chipText entry consistent with the
existing style.
🧹 Nitpick comments (5)
stream-chat-android-previewdata/src/main/kotlin/io/getstream/chat/android/previewdata/PreviewReactionData.kt (1)

52-64: Use a fixed preview timestamp to keep previews/tests deterministic.

Date() makes the preview data time-dependent; a stable value keeps snapshots and previews consistent.

♻️ Suggested change
 public val manyReaction: List<Reaction> = listOf(reaction1, reaction2, reaction3, reaction4)

+    private val previewReactionDate = Date(0L)
+
 public val oneReactionGroup: Map<String, ReactionGroup> = oneReaction.toReactionGroups()
@@
-                firstReactionAt = Date(),
-                lastReactionAt = Date(),
+                firstReactionAt = previewReactionDate,
+                lastReactionAt = previewReactionDate,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-previewdata/src/main/kotlin/io/getstream/chat/android/previewdata/PreviewReactionData.kt`
around lines 52 - 64, The preview data uses Date() in
List<Reaction>.toReactionGroups(), which yields time-dependent timestamps;
replace Date() with a fixed deterministic timestamp (e.g., a shared constant
like PREVIEW_DATE or Date(0)) so firstReactionAt and lastReactionAt are stable
for previews/tests; update the toReactionGroups() implementation (and any
references such as oneReactionGroup and manyReactionGroups) to use that fixed
timestamp constant.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/selectedmessage/SelectedReactionsMenu.kt (3)

77-94: Consider skipPartiallyExpanded = true to match the fillMaxHeight() content

rememberModalBottomSheetState() defaults to skipPartiallyExpanded = false, meaning the sheet first opens at half-height. However, the content passed to ReactionsMenuContent uses Modifier.fillMaxHeight(), which forces the content to occupy the full available height. The result is that the partially-expanded stop becomes a full-height column crammed into a half-height sheet — awkward visually. Setting skipPartiallyExpanded = true ensures the sheet always expands to the Expanded state and moves directly to Hidden when dismissed.

♻️ Proposed fix
-    sheetState = rememberModalBottomSheetState(),
+    sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
🤖 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/selectedmessage/SelectedReactionsMenu.kt`
around lines 77 - 94, The modal sheet opens partially while ReactionsMenuContent
uses Modifier.fillMaxHeight(), causing layout mismatch; update the
ModalBottomSheet's sheetState to use
rememberModalBottomSheetState(skipPartiallyExpanded = true) so the sheet skips
the half-expanded stop and goes straight to expanded (and Hidden on dismiss);
locate the ModalBottomSheet instantiation and replace the current
rememberModalBottomSheetState() call with
rememberModalBottomSheetState(skipPartiallyExpanded = true) while leaving other
parameters (onDismissRequest, dragHandle, etc.) unchanged.

169-179: Trailing Spacer is emitted after the last item

The Spacer(modifier = Modifier.height(8.dp)) inside the forEach loop is appended after every row including the final one. This adds unnecessary extra bottom padding inside the scrollable column. Using verticalArrangement = Arrangement.spacedBy(8.dp) on the Column (removing the manual Spacer) would be cleaner and avoid the trailing gap.

♻️ Proposed fix
 Column(
     horizontalAlignment = Alignment.CenterHorizontally,
+    verticalArrangement = Arrangement.spacedBy(8.dp),
     modifier = modifier
         ...
 ) {
     ...
     userReactions.forEach { item ->
         UserReactionRow(
             item = item,
             onClick = if (item.isMine) {
                 { onReactionOptionSelected(item.type) }
             } else {
                 null
             },
         )
-        Spacer(modifier = Modifier.height(8.dp))
     }
 }
🤖 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/selectedmessage/SelectedReactionsMenu.kt`
around lines 169 - 179, The loop in SelectedReactionsMenu emits a trailing
Spacer after the last UserReactionRow causing extra bottom padding; remove the
Spacer from the userReactions.forEach block and instead set verticalArrangement
= Arrangement.spacedBy(8.dp) on the surrounding Column (where UserReactionRow
items are rendered) so spacing is applied only between items; keep the existing
conditional onClick logic for UserReactionRow (item.isMine) unchanged.

84-84: dragHandle assignment is redundant

ModalBottomSheet's dragHandle parameter already defaults to { BottomSheetDefaults.DragHandle() }, so this explicit assignment can be removed.

♻️ Proposed simplification
-        dragHandle = { BottomSheetDefaults.DragHandle() },
🤖 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/selectedmessage/SelectedReactionsMenu.kt`
at line 84, Remove the redundant explicit dragHandle assignment from the
ModalBottomSheet call in SelectedReactionsMenu (the line setting dragHandle = {
BottomSheetDefaults.DragHandle() }); ModalBottomSheet already defaults to that
drag handle, so simply delete the dragHandle parameter and leave the rest of the
ModalBottomSheet invocation intact (look for the ModalBottomSheet(...) in
SelectedReactionsMenu.kt).
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/MessagesScreen.kt (1)

503-530: Redundant null-safe call after smart cast (line 528)

selectedMessageState is already smart-cast to non-null SelectedMessageReactionsState by the is check on line 503, so ?.ownCapabilities ?: setOf() can be simplified.

♻️ Proposed simplification
-            ownCapabilities = selectedMessageState?.ownCapabilities ?: setOf(),
+            ownCapabilities = selectedMessageState.ownCapabilities,
🤖 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/messages/MessagesScreen.kt`
around lines 503 - 530, The code redundantly uses a null-safe access on
selectedMessageState after an is-check; update the ReactionsMenu call to use the
smart-cast directly by replacing selectedMessageState?.ownCapabilities ?:
setOf() with selectedMessageState.ownCapabilities (or
selectedMessageState.ownCapabilities.ifEmpty { setOf() } if you want a non-empty
fallback) so the ownCapabilities parameter references the non-null
SelectedMessageReactionsState instance used in this branch (within the
ChatTheme.componentFactory.ReactionsMenu invocation).
🤖 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-compose/api/stream-chat-android-compose.api`:
- Around line 3648-3651: Update the release notes and add a small migration
shim: document that StreamColors' primary constructor and copy() now take
StreamLegacyColors as the first parameter (affecting destructuring/component1
and copy usage) and provide explicit migration examples using the new signature
and the library factory functions defaultColors() and defaultDarkColors();
additionally, add a deprecated convenience wrapper (e.g., a deprecated factory
or overload) that accepts the old parameters and delegates to the new
constructor to ease migration for downstream consumers, and reference
StreamColors, StreamLegacyColors, copy(), defaultColors(), and
defaultDarkColors() in the changelog entry.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/selectedmessage/ReactionCountRow.kt`:
- Around line 131-136: The ReactionCountRow currently renders the count even
when it's 1, contradicting its KDoc; update the rendering guard in
ReactionCountRow (where count is checked now with count?.toString()?.let) to
only show the Text when count is non-null and greater than 1 (e.g., check count
!= null && count > 1) and use count.toString() for the displayed text so single
reactions (count == 1) are hidden.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/selectedmessage/UserReactionRow.kt`:
- Around line 71-86: The "Tap to remove" hint in UserReactionRow is shown
whenever item.isMine is true even if the row is not clickable; update the
conditional that renders the remove hint so it only shows when the row is
clickable by checking the onClick parameter as well (e.g., in UserReactionRow,
change the remove-hint branch to require item.isMine && onClick != null) so the
hint is hidden when onClick is null.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/StreamColors.kt`:
- Around line 378-382: In the dark ColorPalette entries, replace the light
resource references used for media shimmer with their dark variants: update
mediaShimmerBase to use R.color.stream_compose_input_background_dark and
mediaShimmerHighlights to use R.color.stream_compose_app_background_dark (the
other dark usages like threadSeparatorGradientStart/End and
imageBackgroundMessageList are already correct) so shimmer colors are
appropriate for dark mode.
- Around line 462-490: Add/update the KDoc for the public data class
StreamLegacyColors to state it is an immutable, thread-safe representation of
legacy theme colors and should not hold mutable state; mention that instances
are expected to be treated as read-only (safe for concurrent access) and update
any existing KDoc above StreamLegacyColors to include this note and a brief
guidance for clients to create new instances for changes rather than mutating
shared objects.

---

Outside diff comments:
In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/StreamColors.kt`:
- Around line 25-114: The class KDoc is missing a parameter description for the
public color chipText; update the StreamColors KDoc to add a `@param` entry named
chipText explaining its purpose (e.g., color used for text inside chips/pills),
typical usage context, and any state/thread expectations, so the public API is
fully documented; locate the KDoc block at the top of StreamColors.kt where
other `@param` tags are listed and insert the chipText entry consistent with the
existing style.

---

Nitpick comments:
In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/selectedmessage/SelectedReactionsMenu.kt`:
- Around line 77-94: The modal sheet opens partially while ReactionsMenuContent
uses Modifier.fillMaxHeight(), causing layout mismatch; update the
ModalBottomSheet's sheetState to use
rememberModalBottomSheetState(skipPartiallyExpanded = true) so the sheet skips
the half-expanded stop and goes straight to expanded (and Hidden on dismiss);
locate the ModalBottomSheet instantiation and replace the current
rememberModalBottomSheetState() call with
rememberModalBottomSheetState(skipPartiallyExpanded = true) while leaving other
parameters (onDismissRequest, dragHandle, etc.) unchanged.
- Around line 169-179: The loop in SelectedReactionsMenu emits a trailing Spacer
after the last UserReactionRow causing extra bottom padding; remove the Spacer
from the userReactions.forEach block and instead set verticalArrangement =
Arrangement.spacedBy(8.dp) on the surrounding Column (where UserReactionRow
items are rendered) so spacing is applied only between items; keep the existing
conditional onClick logic for UserReactionRow (item.isMine) unchanged.
- Line 84: Remove the redundant explicit dragHandle assignment from the
ModalBottomSheet call in SelectedReactionsMenu (the line setting dragHandle = {
BottomSheetDefaults.DragHandle() }); ModalBottomSheet already defaults to that
drag handle, so simply delete the dragHandle parameter and leave the rest of the
ModalBottomSheet invocation intact (look for the ModalBottomSheet(...) in
SelectedReactionsMenu.kt).

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/MessagesScreen.kt`:
- Around line 503-530: The code redundantly uses a null-safe access on
selectedMessageState after an is-check; update the ReactionsMenu call to use the
smart-cast directly by replacing selectedMessageState?.ownCapabilities ?:
setOf() with selectedMessageState.ownCapabilities (or
selectedMessageState.ownCapabilities.ifEmpty { setOf() } if you want a non-empty
fallback) so the ownCapabilities parameter references the non-null
SelectedMessageReactionsState instance used in this branch (within the
ChatTheme.componentFactory.ReactionsMenu invocation).

In
`@stream-chat-android-previewdata/src/main/kotlin/io/getstream/chat/android/previewdata/PreviewReactionData.kt`:
- Around line 52-64: The preview data uses Date() in
List<Reaction>.toReactionGroups(), which yields time-dependent timestamps;
replace Date() with a fixed deterministic timestamp (e.g., a shared constant
like PREVIEW_DATE or Date(0)) so firstReactionAt and lastReactionAt are stable
for previews/tests; update the toReactionGroups() implementation (and any
references such as oneReactionGroup and manyReactionGroups) to use that fixed
timestamp constant.

@gpunto gpunto force-pushed the redesign/user-reactions-sheet branch from 29bd675 to 6a9f716 Compare February 20, 2026 10:21
@gpunto gpunto force-pushed the redesign/user-reactions-sheet branch from 6a9f716 to 444e6eb Compare February 20, 2026 10:33
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
71.1% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@gpunto gpunto merged commit 865ff15 into v7 Feb 20, 2026
13 of 15 checks passed
@gpunto gpunto deleted the redesign/user-reactions-sheet branch February 20, 2026 11:07
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.

2 participants