Channel pinning: list indicator and sheet header icons#6474
Conversation
PR checklist ✅All required conditions are satisfied:
🎉 Great job! This PR is ready for review. |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
SDK Size Comparison 📏
|
WalkthroughThis PR adds channel pinning support to Stream Chat Android Compose, enabling users to pin important channels to the top of lists. The implementation spans configuration (new ChangesChannel Pinning Feature Implementation
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
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: 3
🤖 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-compose/api/stream-chat-android-compose.api`:
- Around line 2909-2920: The new pinIndicatorPosition was inserted in the middle
of the ChannelListConfig primary constructor and generated members
(constructors, copy, componentN), changing the parameter order and breaking
positional/destructuring callers; move pinIndicatorPosition to the end of the
primary constructor and data class property list (so ChannelListConfig keeps
parameters order: MuteIndicatorPosition, Boolean, ChannelOptionsVisibility, then
PinIndicatorPosition as trailing parameter with a default), regenerate/update
the synthetic constructor/copy/componentN ordering accordingly so existing
callers remain source-compatible while exposing the new property.
- Line 890: The public API lost an overload for SelectedChannelMenu when two
Boolean parameters (isMuted, isPinned) were inserted before the headerContent
and centerContent lambdas; add back the old overload (the previous
SelectedChannelMenu signature that accepts Channel, User, List, Function1
headerContent, Function0 ??? — specifically the variant that callers used
positionally for headerContent and centerContent) and implement it as a thin
forwarder to the new SelectedChannelMenu implementation, passing sensible
default values for isMuted and isPinned and forwarding all other args (including
headerContent and centerContent) to preserve binary/Source compatibility; locate
the new implementation named SelectedChannelMenu (mangled in the diff as
SelectedChannelMenu-j30j4ZQ) and create the overload that calls it with default
flags so existing positional callers continue to compile.
In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatUiConfig.kt`:
- Line 64: The default for pinIndicatorPosition in ChatUiConfig is set to
PinIndicatorPosition.InlineTitle but the redesign intends trailing placement;
update the declaration of pinIndicatorPosition (in ChatUiConfig) to use
PinIndicatorPosition.Trailing as the default so the out-of-the-box behavior
matches the PR objective.
🪄 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: 040c5896-77b9-47a8-bfae-6827572b3352
⛔ Files ignored due to path filters (10)
stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channels_ChannelItemTest_muted_and_pinned_channel.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channels_ChannelItemTest_muted_and_pinned_channel_mixed_positions.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channels_ChannelItemTest_muted_and_pinned_channel_trailing_bottom.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channels_ChannelItemTest_muted_channel_trailing_bottom.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channels_ChannelItemTest_pinned_channel.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channels_ChannelItemTest_pinned_channel_trailing_bottom.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channels_SelectedChannelMenuTest_selected_channel.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channels_SelectedChannelMenuTest_selected_channel_centered_dialog.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channels_SelectedChannelMenuTest_selected_channel_in_dark_mode.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channels_SelectedChannelMenuTest_selected_channel_muted_and_pinned.pngis excluded by!**/*.png
📒 Files selected for processing (26)
stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/data/CustomSettings.ktstream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/feature/channel/list/ChannelsActivity.ktstream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/chats/ChatsActivity.ktstream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/login/CustomLoginActivity.ktstream-chat-android-compose-sample/src/main/res/values/strings.xmlstream-chat-android-compose/api/stream-chat-android-compose.apistream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/ChannelsScreen.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/info/SelectedChannelMenu.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/ChannelItem.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/ChannelOptions.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactoryParams.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatUiConfig.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/ChannelUtils.ktstream-chat-android-compose/src/main/res/values-es/strings.xmlstream-chat-android-compose/src/main/res/values-fr/strings.xmlstream-chat-android-compose/src/main/res/values-hi/strings.xmlstream-chat-android-compose/src/main/res/values-in/strings.xmlstream-chat-android-compose/src/main/res/values-it/strings.xmlstream-chat-android-compose/src/main/res/values-ja/strings.xmlstream-chat-android-compose/src/main/res/values-ko/strings.xmlstream-chat-android-compose/src/main/res/values/strings.xmlstream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/channels/ChannelItemTest.ktstream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/channels/SelectedChannelMenuTest.ktstream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/util/ChannelUtilsTest.ktstream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/channels/SelectedChannelMenu.kt
💤 Files with no reviewable changes (1)
- stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/channels/SelectedChannelMenu.kt
96d7e52 to
0678778
Compare
|
Aligns the DM action order with the channel-pinning redesign: View Info -> Pin/Unpin Chat -> Mute/Unmute User -> Block/Unblock User -> Archive/Unarchive Chat -> Delete Chat. Group order was already correct; only KDoc refreshed to reflect the actual default flow and the opt-in nature of Pin and Archive.
The selected-channel menu header now renders inline mute and pin icons next to the channel name when the respective attribute is active, in a fixed name -> mute -> pin order matching the redesign. Adds defaulted isMuted/isPinned fields to ChannelMenuParams and ChannelMenuHeaderContentParams; ChannelsScreen composes the channel-mute OR DM-counterpart-user-mute signal inline using existing isChannelMuted/isUserMuted accessors. Extracts a small internal Channel.dmCounterpartId helper to remove a duplicate member-iteration that also appeared in buildDmChannelActions. Localizes stream_compose_channel_item_pinned across the seven supported locales. Enables isPinChannelVisible in the compose sample so the behaviour is reachable from the dogfooding app. The preview-vs-snapshot duplication for SelectedChannelMenu is removed via three internal content composables called by both the previews and the Paparazzi tests.
Adds a PinIndicatorPosition enum mirroring MuteIndicatorPosition, plus a
trailing pinIndicatorPosition field on ChannelListConfig so the pin icon
can be placed inline with the channel name or at the trailing bottom of
the preview row, independent of the mute icon position. The render order
in TitleRow is name -> mute -> pin (matching the redesign) and the trailing
branch in MessageRow renders mute before pin. The mute/pin icon rendering
is extracted into MutedIcon/PinnedIcon helpers and the preview/snapshot
config overrides now use CompositionLocalProvider(LocalChatUiConfig) to
keep snapshotWithDarkMode's dark-mode flag intact across nested overrides.
In the sample, channel pinning becomes a CustomSettings feature flag
(isChannelPinningEnabled, default false) toggled from the Custom Login
settings panel and consumed by both ChannelsActivity and ChatsActivity.
Activities adopt the canonical settings field pattern via
private val settings by lazy { customSettings() }.
Snapshot baselines: five new ChannelItem tests (pinned only, pinned
trailing, muted+pinned both inline, muted+pinned both trailing, mixed)
and a refreshed muted_channel_trailing_bottom to fix a pre-existing
dark-mode rendering bug exposed by the same nested-ChatTheme issue.
Also folds in KDoc cleanup on the muted/pinned param docs added in the
previous commit, removing implementation rationale per the engineering
principles.
Composes the existing last_updated sort with a leading desc("pinned_at")
in both ChannelsActivity and ChatsActivity so pinned channels surface at
the top of the channel list while unpinned channels keep their
recent-activity ordering. Acts as the recipe integrators can mirror to
enable pinned-first sorting without a SDK-default change.
…ions Adds a new public overload of buildDefaultChannelActions without the isMuted parameter and marks the previous five-arg overload @deprecated with a ReplaceWith quick-fix. The parameter was received but never read by either the DM or group action builders. ChannelsScreen, the sample ChannelsActivity, and the docs example are updated to call the new overload so the SDK does not dogfood the deprecated API.
Drops the isMuted/isPinned params on ChannelMenuParams, ChannelMenuHeaderContentParams, and SelectedChannelMenu. The default header now derives the pinned state from selectedChannel.isPinned() and the muted state from currentUser.channelMutes (channel-level) and currentUser.mutes (DM-counterpart) via a private helper. ChannelsScreen reverts to its pre-PR shape — no OR computation, no flags on the params. Addresses the review concern that isMuted was ambiguous (channel vs. user mute) and that both flags duplicate state the menu already has via selectedChannel + currentUser. Net effect on the public API vs. develop is zero new fields on the params data classes for icon state.
Aligns ChatsActivity with ChannelsActivity by using the
android_sample_filter predefined filter (which already sorts pinned
channels to the top server-side) instead of an inline filter + client
querySort. Drops the now-unused CHANNEL_ARG_DRAFT, Filters, and
QuerySortByField imports from ChatsActivity.
Also updates the channelsViewModelFactory KDoc in ChannelsActivity to
reflect the actual sort applied by the predefined filter
(QuerySortByField<Channel>().desc("pinned_at").desc("last_updated")).
0678778 to
06a42cd
Compare



Closes AND-1185
Goal
Implement the channel pinning visual redesign across the Compose SDK: render mute and pin icons on the channel list item and the
SelectedChannelMenuheader, surface Pin/Unpin Chat as a first-class action in the channel-options sheet, and expose a configurable indicator position so integrators can mix inline and trailing-bottom layouts per the Figma cookbook.docs https://github.com/GetStream/docs-content/pull/1312
Implementation
Channel-options sheet (
ChannelOptions.kt)View Info → Pin/Unpin Chat → Mute/Unmute User → Block/Unblock User → Archive → Delete. Group order already matched; refreshed the stale KDoc.isPinChannelVisible = falsedefault preserved — the feature stays opt-in.Channel.dmCounterpartIdhelper to remove duplicated member iteration inbuildDmChannelActions.SelectedChannelMenuheader iconsisMutedandisPinneddefaulted parameters toSelectedChannelMenu,ChannelMenuParams, andChannelMenuHeaderContentParams.name → mute → pinorder when either flag is on. Each icon owns its owncontentDescriptionat the leaf (no parent-level semantic override).ChannelsScreencomposesisChannelMuted(cid) || isUserMuted(dmCounterpartId)at the call site — same OR the channel-list row uses — so the sheet header mirrors the row state.Channel list item pin indicator (
ChannelItem.kt,ChatUiConfig.kt)PinIndicatorPositionenum (InlineTitle,TrailingBottom) and a newpinIndicatorPositionfield onChannelListConfig(default valueInlineTitle). Mirrors the existingMuteIndicatorPositionshape so the two attributes are configured independently — integrators can mix positions (mute inline + pin trailing, etc.).MutedIcon/PinnedIconprivate composables to share rendering between the inline and trailing branches and keepTitleRowunder detekt'sLongMethodthreshold.CompositionLocalProvider(LocalChatUiConfig provides …)(matching theMessageComposerInputLinkpattern). This also fixes a pre-existing dark-mode rendering bug inmuted_channel_trailing_bottomcaused by the previous nested-ChatThemeapproach defaultingisInDarkModeback to the system value.Helpers
internal fun Channel.dmCounterpartId(currentUser: User?): String?inChannelUtils.kt. Used by bothbuildDmChannelActionsandChannelsScreen. Covered byChannelUtilsTest(5 cases: DM with counterpart, non-distinct channel, distinct 3-member, distinct 2-member without current user, null current user).Localization
stream_compose_channel_item_pinnedadded to the eight supported locale files (default + es, fr, hi, in, it, ja, ko).Compose sample
CustomSettings.isChannelPinningEnabledboolean pref (defaultfalse).FeatureFlagtoggle in the Custom Login settings panel, ordered between Adaptive layout and the composer flags to follow the app's screen-surface flow.ChannelsActivityandChatsActivityread the flag to driveChannelOptionsVisibility.isPinChannelVisible, and both compose a pinned-first sortQuerySortByField<Channel>().desc("pinned_at").desc("last_updated")as the recipe integrators can mirror.private val settings by lazy { customSettings() }field convention (matchesCustomLoginActivity,ChannelActivity).API surface
Additive only —
PinIndicatorPositionenum,pinIndicatorPositionfield,isMuted/isPinnedfields on the two params data classes, andSelectedChannelMenudefaulted params. All new fields are trailing parameters with default values, so existing callers compile unchanged.🎨 UI Changes
Screen_recording_20260525_155105.webm
Screen_recording_20260525_155105.webm
Screen_recording_20260525_162627.webm
Screen_recording_20260525_162711.webm
Testing
stream-chat-android-compose-sampleapp and open Custom Login.View Info → Pin Chat → Leave Group → Delete Group(assuming owner capabilities).View Info → Pin/Unpin Chat → Mute/Unmute User → Block/Unblock User → Delete Chat.ChannelListConfig(pinIndicatorPosition = PinIndicatorPosition.TrailingBottom)inChannelsActivity. Confirm the pin icon now renders at the trailing end of the message-preview row.muteIndicatorPosition = InlineTitleandpinIndicatorPosition = TrailingBottomon the sameChannelListConfig. Confirm the muted icon stays inline with the name while the pin icon moves to the trailing-bottom corner.Snapshot baselines for the five new pin variants plus the refreshed
muted_channel_trailing_bottombaseline are recorded instream-chat-android-compose/src/test/snapshots/images/.Summary by CodeRabbit
Release Notes
New Features
Tests