Align modal bottom sheets with Figma#6480
Conversation
Fixes the dark-mode regression on the attachment command picker (it inherited M3's light surfaceContainerLow because the callsite didn't pass containerColor). All nine ModalBottomSheet callsites now go through StreamCardBottomSheet or StreamScreenBottomSheet, which bake the Figma-aligned tokens (backgroundCoreElevation1/backgroundCoreApp, backgroundCoreScrim, radius/4xl, drag handle, sheetMaxWidth). Adds light + dark snapshot coverage for every modal bottom sheet in the SDK.
Replaces SelectedChannelMenu (now @deprecated, removed in v8) with a Material 3 bottom sheet built on StreamCardBottomSheet. The old menu stays for backwards compatibility — its shape/overlayColor/centered-Card path keeps working. Migrates MediaGalleryPhotosMenu, PollAnswers, and PollMoreOptionsDialog from hand-rolled Box+Surface/Popup implementations to the Stream wrappers, inheriting M3 semantics (drag handle, swipe-to-dismiss, scrim) and the design tokens. Drops ChannelsScreen's redundant AnimatedVisibility since the new sheet supplies its own animation. Plumbs ChannelMenuHeaderContentParams.modifier (previously dead) through the factory so the deprecated SelectedChannelMenu can express the 16dp top inset it needs (no drag handle), while ChannelActionsSheet leaves it empty.
Moves DefaultChannelMenuHeaderContent (renamed from DefaultSelectedChannelMenuHeaderContent) and its private helpers to ChannelActionsSheet.kt. When SelectedChannelMenu is removed in v8, the old file becomes a single git rm. No behavior change — the factory still delegates to the same function.
- Wrap @Preview calls in Box(Modifier.fillMaxSize()) so the underlying Dialog has bounds to compute sheet anchors; without it M3 skips its show animation and the preview stays blank. - Add rememberStreamSheetState: under LocalInspectionMode it returns a pre-expanded SheetState. Paparazzi captures a single frame and won't tick M3's show LaunchedEffect, so a Hidden-initial sheet renders blank. - Drop the body-extraction workaround on ChannelActionsSheet now that the real sheet path renders through both surfaces.
The StreamCardBottomSheet wrapper already includes the M3 drag handle, which provides the top inset. The hardcoded top padding stacked on top of that and misaligned the avatar against sibling sheets.
PR checklist ✅All required conditions are satisfied:
🎉 Great job! This PR is ready for review. |
SDK Size Comparison 📏
|
|
|
@CodeRabbit review |
✅ Actions performedReview triggered.
|
The previous content-level Sample (`ReactionsMenuContentOneReaction` / `ManyReactions`) bypassed the public `ReactionsMenu` composable and the StreamCardBottomSheet wrapper — they predate sheet-level rendering being viable in @Preview and Paparazzi. Now that both surfaces render the real sheet path, drive the previews and snapshots through `ReactionsMenuSample*` instead, which calls the public `ReactionsMenu` end-to-end. Closes the 0% new-code coverage gap on the file. Renames `ReactionsMenuContentTest` → `ReactionsMenuTest` and relocates it under `ui.components.messageactions` (the package of the file under test). Switches from `snapshotWithDarkMode` to `snapshot` + light/dark pairs, matching the pattern used by every other sheet test.
WalkthroughThis PR consolidates bottom-sheet handling across the SDK by introducing reusable ChangesBottom-Sheet Refactoring and Channel Actions UI
Test Snapshot Coverage Updates
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 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)
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.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/feature/channel/list/ChannelsActivity.kt (1)
358-368:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winDismiss the sheet before handling
ViewInfo.Line 362 passes
::viewChannelInfostraight intobuildDefaultChannelActions, so the sample skips thedismissChannelAction()call thatChannelsScreenperforms before navigation. That leavesselectedChannelset when the user comes back from the info screen, so the sheet can reopen immediately.Suggested fix
val channelActions = buildDefaultChannelActions( selectedChannel = selectedChannel, ownCapabilities = selectedChannel.ownCapabilities, viewModel = channelsViewModel, - onViewInfoAction = ::viewChannelInfo, + onViewInfoAction = { channel -> + channelsViewModel.dismissChannelAction() + viewChannelInfo(channel) + }, )🤖 Prompt for 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. In `@stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/feature/channel/list/ChannelsActivity.kt` around lines 358 - 368, The sample passes ::viewChannelInfo directly into buildDefaultChannelActions which skips dismissing the sheet first; change the callback passed for the "view info" action to a lambda that first invokes channelsViewModel.dismissChannelAction() (or channelsViewModel.dismissChannelAction(Unit) as appropriate) and then calls viewChannelInfo(selectedChannel) so the sheet is cleared before navigation; update the call site where buildDefaultChannelActions is invoked to pass this wrapped lambda instead of ::viewChannelInfo.stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/poll/PollViewResultDialog.kt (1)
95-120:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftAvoid nesting
StreamScreenBottomSheetinside the results sheet.When
showAllOptionVotesis non-null, this branch rendersPollOptionVotesDialoginside the outerStreamScreenBottomSheet, butPollOptionVotesDialognow creates its ownStreamScreenBottomSheet. That gives the “Show All” flow two modal sheets at once, which is likely to produce stacked scrims and conflicting back/dismiss behavior. Please switch the outer sheet’s content instead of instantiating a second sheet here.🤖 Prompt for 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. In `@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/poll/PollViewResultDialog.kt` around lines 95 - 120, The outer StreamScreenBottomSheet currently remains active while showAllOptionVotes is non-null, and the code calls PollOptionVotesDialog which itself creates a second StreamScreenBottomSheet, causing stacked sheets; fix it by switching the outer sheet’s content when showAllOptionVotes != null instead of instantiating a nested sheet: inside the Crossfade targetState branch (where showAllOptionVotes/option is handled) either render the inner dialog UI directly (extract PollOptionVotesDialog’s content into a composable like PollOptionVotesContent and call that here) or add a parameter to PollOptionVotesDialog (e.g., wrapInSheet: Boolean) and call PollOptionVotesDialog(..., wrapInSheet = false) so no second StreamScreenBottomSheet is created; update calls to PollOptionVotesDialog and any back/dismiss handlers (onDismissRequest/onBackPressed) to set showAllOptionVotes = null accordingly.
🤖 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.
Outside diff comments:
In
`@stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/feature/channel/list/ChannelsActivity.kt`:
- Around line 358-368: The sample passes ::viewChannelInfo directly into
buildDefaultChannelActions which skips dismissing the sheet first; change the
callback passed for the "view info" action to a lambda that first invokes
channelsViewModel.dismissChannelAction() (or
channelsViewModel.dismissChannelAction(Unit) as appropriate) and then calls
viewChannelInfo(selectedChannel) so the sheet is cleared before navigation;
update the call site where buildDefaultChannelActions is invoked to pass this
wrapped lambda instead of ::viewChannelInfo.
In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/poll/PollViewResultDialog.kt`:
- Around line 95-120: The outer StreamScreenBottomSheet currently remains active
while showAllOptionVotes is non-null, and the code calls PollOptionVotesDialog
which itself creates a second StreamScreenBottomSheet, causing stacked sheets;
fix it by switching the outer sheet’s content when showAllOptionVotes != null
instead of instantiating a nested sheet: inside the Crossfade targetState branch
(where showAllOptionVotes/option is handled) either render the inner dialog UI
directly (extract PollOptionVotesDialog’s content into a composable like
PollOptionVotesContent and call that here) or add a parameter to
PollOptionVotesDialog (e.g., wrapInSheet: Boolean) and call
PollOptionVotesDialog(..., wrapInSheet = false) so no second
StreamScreenBottomSheet is created; update calls to PollOptionVotesDialog and
any back/dismiss handlers (onDismissRequest/onBackPressed) to set
showAllOptionVotes = null accordingly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 10987f75-6190-4f69-9fdd-595a1a618606
⛔ Files ignored due to path filters (49)
stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.preview.internal_MediaGalleryPhotosMenuTest_photos_menu.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.preview.internal_MediaGalleryPhotosMenuTest_photos_menu_in_dark_mode.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.preview_MediaGalleryPreviewOptionsMenuTest_media_gallery_options_menu_for_other_user.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.preview_MediaGalleryPreviewOptionsMenuTest_media_gallery_options_menu_for_other_user_in_dark_mode.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.preview_MediaGalleryPreviewOptionsMenuTest_media_gallery_options_menu_for_own_user.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.preview_MediaGalleryPreviewOptionsMenuTest_media_gallery_options_menu_for_own_user_in_dark_mode.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.preview_MediaGalleryPreviewScreenTest_media_gallery_screen_with_gallery_bottom_sheet.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.preview_MediaGalleryPreviewScreenTest_media_gallery_screen_with_options_menu.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channel.attachments_ChannelMediaAttachmentsPreviewSheetTest_preview_sheet.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channel.attachments_ChannelMediaAttachmentsPreviewSheetTest_preview_sheet_in_dark_mode.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channel.info_ChannelInfoMemberInfoModalSheetTest_banned_member.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channel.info_ChannelInfoMemberInfoModalSheetTest_banned_member_in_dark_mode.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channel.info_ChannelInfoMemberInfoModalSheetTest_not_banned_member.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channel.info_ChannelInfoMemberInfoModalSheetTest_not_banned_member_in_dark_mode.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channel.info_GroupChannelEditScreenTest_image_picker_no_remove.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channel.info_GroupChannelEditScreenTest_image_picker_no_remove_in_dark_mode.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channel.info_GroupChannelEditScreenTest_image_picker_no_remove_in_light_mode.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channel.info_GroupChannelEditScreenTest_image_picker_with_remove.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channel.info_GroupChannelEditScreenTest_image_picker_with_remove_in_dark_mode.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channel.info_GroupChannelEditScreenTest_image_picker_with_remove_in_light_mode.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channels.info_ChannelActionsSheetTest_channel_actions_sheet.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channels.info_ChannelActionsSheetTest_channel_actions_sheet_in_dark_mode.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_centered_dialog_in_dark_mode.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!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.channels_SelectedChannelMenuTest_selected_channel_muted_and_pinned_in_dark_mode.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.poll_PollAnswersTest_closed_anonymous.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.poll_PollAnswersTest_closed_anonymous_in_dark_mode.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.poll_PollAnswersTest_content.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.poll_PollAnswersTest_content_in_dark_mode.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.poll_PollAnswersTest_with_current_user_answer.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.poll_PollAnswersTest_with_current_user_answer_in_dark_mode.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.poll_PollMoreOptionsDialogTest_more_options.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.poll_PollMoreOptionsDialogTest_more_options_in_dark_mode.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.poll_PollOptionVotesDialogTest_content.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.poll_PollOptionVotesDialogTest_content_in_dark_mode.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.poll_PollOptionVotesDialogTest_loading.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.poll_PollOptionVotesDialogTest_loading_in_dark_mode.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.poll_PollOptionVotesDialogTest_loading_more.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.poll_PollOptionVotesDialogTest_loading_more_in_dark_mode.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.poll_PollViewResultDialogTest_poll_results.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.poll_PollViewResultDialogTest_poll_results_in_dark_mode.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.reactionpicker_ReactionsPickerTest_Default_reaction_picker_content.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.reactionpicker_ReactionsPickerTest_reaction_picker.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.reactionpicker_ReactionsPickerTest_reaction_picker_in_dark_mode.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages.attachments_AttachmentSystemPickerTest_command_picker_sheet.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages.attachments_AttachmentSystemPickerTest_command_picker_sheet_in_dark_mode.pngis excluded by!**/*.png
📒 Files selected for processing (33)
stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/feature/channel/list/ChannelsActivity.ktstream-chat-android-compose/api/stream-chat-android-compose.apistream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/MediaGalleryPreviewOptionsMenu.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/internal/MediaGalleryPhotosMenu.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channel/attachments/ChannelMediaAttachmentsGrid.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channel/info/ChannelInfoMemberInfoModalSheet.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channel/info/GroupChannelEditScreen.ktstream-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/ChannelActionsSheet.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/components/SimpleMenu.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/StreamModalBottomSheet.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messageactions/ReactionsMenu.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/poll/PollAnswers.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/poll/PollMoreOptionsDialog.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/poll/PollOptionVotesDialog.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/poll/PollViewResultDialog.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/reactionpicker/ReactionsPicker.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentSystemPicker.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.ktstream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/attachments/preview/MediaGalleryPreviewOptionsMenuTest.ktstream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/attachments/preview/internal/MediaGalleryPhotosMenuTest.ktstream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/channel/attachments/ChannelMediaAttachmentsPreviewSheetTest.ktstream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/channel/info/ChannelInfoMemberInfoModalSheetTest.ktstream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/channel/info/GroupChannelEditScreenTest.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/channels/info/ChannelActionsSheetTest.ktstream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/components/poll/PollAnswersTest.ktstream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/components/poll/PollMoreOptionsDialogTest.ktstream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/components/poll/PollOptionVotesDialogTest.ktstream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/components/poll/PollViewResultDialogTest.ktstream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/components/reactionpicker/ReactionsPickerTest.ktstream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentSystemPickerTest.kt
Adds two sample/preview/test variants that the deprecated SelectedChannelMenuTest exercises but ChannelActionsSheetTest didn't yet: muted+pinned channel state (verifies the inline mute and pin icons render in the header) and a null-currentUser variant (exercises the `currentUser: User? = null` default branch). Combined with the existing default-render tests, ChannelActionsSheetTest now has 6 baselines (3 scenarios × light/dark), matching SelectedChannelMenuTest's scope. The centered-dialog variant doesn't translate — ChannelActionsSheet is a real M3 Dialog and is always bottom-aligned.
The deprecated SelectedChannelMenu doesn't need to be Figma-aligned — that's ChannelActionsSheet's job. Reverts the public shape default from the 32dp four-corner StreamTokens shape back to develop's `RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)`, plus the matching change in the internal SelectedChannelMenuSample preview helper. The 4 affected SelectedChannelMenuTest baselines are re-recorded; centered-dialog baselines untouched (they pass an explicit shape). The @deprecated annotation, the headerContent modifier with top padding (required because the relocated DefaultChannelMenuHeaderContent no longer has inline top padding and SimpleMenu has no drag handle), and the function relocations to ChannelActionsSheet.kt all remain.
The shape default on SelectedChannelMenuSample and the dropped explicit shape args at the two call sites were churn from when the public default was 32dp. Now that the public default is back to 16dp, restore SelectedChannelMenuSample to develop's signature (shape required, after alignment) and pass the explicit shape at the two call sites. Snapshots unchanged — same render, fewer modified lines.


Closes AND-1203
Goal
Align every modal bottom sheet in the Compose SDK with the Figma design. The audit started from a dark-mode regression on the attachment command picker (it inherited M3's light
surfaceContainerLowbecause the callsite didn't pass acontainerColor) and revealed that modal bottom sheets across the SDK had each chosen their own values for shape, scrim, container color, and drag handle — none matching the Figma tokens, and three sheets bypassing M3 entirely withPopup+AnimatedVisibilityorBox+Surface. Three deliverables fall out:StreamCardBottomSheetandStreamScreenBottomSheet— bake the Figma tokens (corner radii, elevation/app surface, scrim, drag handle, max width) in one place. EveryModalBottomSheetcallsite in the SDK now goes through them.ChannelActionsSheetreplacesSelectedChannelMenu(deprecated, removed in v8) as a more discoverable, Figma-aligned channel-actions surface.@Previewand Paparazzi rendering — every sheet has light + dark snapshot coverage so future Figma drift surfaces at PR review instead of in production.Implementation
Two internal wrappers consolidate sheet construction:
StreamCardBottomSheet— card sitting above the app: 32dp top corners,backgroundCoreElevation1container,backgroundCoreScrimscrim, M3 drag handle.StreamScreenBottomSheet— full-takeover surface:sheetMaxWidth = Dp.Unspecified, rectangular, no drag handle,backgroundCoreAppcontainer.Every modal bottom sheet in the SDK now goes through one of those two wrappers, including three that were previously built from
Box+SurfaceorPopup+AnimatedVisibility(MediaGalleryPhotosMenu,PollAnswers,PollMoreOptionsDialog) — they inherit drag handle, swipe-to-dismiss, and scrim from M3 for free.SelectedChannelMenuandSimpleMenuare marked@Deprecated(removal in v8) in favour ofChannelActionsSheet, a public@Composablebuilt onStreamCardBottomSheet. The factory hookChatComponentFactory.ChannelMenudelegates to the new sheet, so integrators usingChannelsScreenpick up the new rendering automatically; the sample'sMyCustomUiis also migrated.SelectedChannelMenukeeps its existing public surface and develop-aligned defaults (16dp shape,overlayColor, slot params) — the Figma redesign lives onChannelActionsSheet, while the deprecated path stays visually close to develop until removal.@Previewcalls wrap each sheet inBox(Modifier.fillMaxSize())so the layout has bounds for M3 to compute the sheet'sExpandedanchor against — without that, M3's internal showLaunchedEffectskips and the preview stays blank. For Paparazzi (single-frame capture; M3's show animation doesn't tick to completion in one frame), a privaterememberStreamSheetStatereturns a pre-expandedSheetStateunderLocalInspectionMode; the IDE preview pane runs the animation on its own, so that swap is exclusively a Paparazzi enabler. Both behaviours are documented in the wrappers' KDoc, including the Google Issue Tracker reference.Also fixes a small visual misalignment:
ChannelInfoMemberInfoModalSheetTopBarwas stacking a hardcodedtop = spacingMdon top of the M3 drag handle's inset.🎨 UI Changes
The rendered output for every modal bottom sheet is captured by the Paparazzi snapshots under
stream-chat-android-compose/src/test/snapshots/images/— reviewing the diff there is the most reliable way to see the before/after across light and dark modes.Testing
Run the Compose sample in dark mode.
surfaceContainerLow.// MyCustomUi()atChannelsActivity.kt:262and re-run. Long-press a channel — confirm it opensChannelActionsSheet(the new API), not the deprecatedSelectedChannelMenu.Automated coverage: Paparazzi snapshots (light + dark) for every modal bottom sheet in the SDK.
./gradlew :stream-chat-android-compose:verifyPaparazziDebugruns all 820 tests green. IDE-preview rendering was spot-checked onChannelActionsSheetandPollMoreOptionsDialog; the same wrapping pattern (Box(Modifier.fillMaxSize())) is applied to every other affected preview.Summary by CodeRabbit
Release Notes
New Features
Improvements
Deprecations