Channel Actions Sheet

The ChannelActionsSheet component displays information about a selected Channel and provides actions the user can take. It shows:

  • Channel name: The formatted name of the selected channel.
  • Channel members: Avatars of the channel members.
  • Member status: Online/offline status or member count.
  • Channel state icons: Inline muted and pinned icons next to the channel name, derived from the channel's pin state and the current user's mute settings.
  • Channel options: Actions like "View Info", "Leave Group", "Delete Conversation", or "Mute Channel" based on user permissions.

It is built on Material 3's ModalBottomSheet, so it inherits drag handle, swipe-to-dismiss, and scrim handling for free. It replaces the previous SelectedChannelMenu component, which is now @Deprecated and slated for removal in v8.

Usage

If you're using the ChannelsScreen component, you don't have to do anything. The factory ChatComponentFactory.ChannelMenu delegates to ChannelActionsSheet, so the new sheet renders automatically when a user long-presses a channel.

If you're building a custom UI, you can add the ChannelActionsSheet component on top of your UI, like so:

val listViewModel: ChannelListViewModel by viewModels { ChannelListViewModelFactory() }

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContent {
        ChatTheme {
            val user by listViewModel.user.collectAsState()
            val selectedChannelState by listViewModel.selectedChannel
            val currentlySelectedChannel = selectedChannelState

            // The rest of your UI

            if (currentlySelectedChannel != null) {
                ChannelActionsSheet(
                    channel = currentlySelectedChannel,
                    actions = buildDefaultChannelActions(
                        selectedChannel = currentlySelectedChannel,
                        ownCapabilities = currentlySelectedChannel.ownCapabilities,
                        viewModel = listViewModel,
                        onViewInfoAction = { /* Handle view info */ },
                    ),
                    onActionClick = { listViewModel.executeOrConfirm(it) },
                    onDismiss = { listViewModel.dismissChannelAction() },
                    currentUser = user,
                )
            }
        }
    }
}

For the ChannelActionsSheet component to work, you need to provide channel and actions. The currentUser parameter is optional — when provided, the default header derives the inline muted/pinned state icons from the current user's mute settings; when null, those icons are not rendered.

Notice that the sheet is rendered as a real Material 3 modal bottom sheet (overlaying everything via its own Dialog window), so unlike the deprecated SelectedChannelMenu, you do not need to wrap it in a full-size Box to control its position — it always sits at the bottom of the screen with the system scrim above.

Handling Actions

The ChannelActionsSheet exposes two required action handlers:

@Composable
fun ChannelActionsSheet(
    ..., // State
    onActionClick: (ChannelAction) -> Unit,
    onDismiss: () -> Unit,
    ..., // Optional state
)
  • onActionClick: Called when the user taps on any channel option. Receives a ChannelAction which can be one of:
    • ViewInfo - View channel details
    • MuteChannel / UnmuteChannel - Mute or unmute notifications
    • PinChannel / UnpinChannel - Pin or unpin the channel
    • ArchiveChannel / UnarchiveChannel - Archive or unarchive the channel
    • LeaveGroup - Leave the group channel
    • DeleteConversation - Delete the conversation
  • onDismiss: Called when the sheet is dismissed — by tapping the scrim, swiping it down, or pressing the system back button.

Here's how ChannelsScreen handles these actions internally:

ChannelActionsSheet(
    ..., // State
    onActionClick = { action ->
        when (action) {
            is ViewInfo -> {
                listViewModel.dismissChannelAction()
                onViewChannelInfoAction(action.channel)
            }
            is MuteChannel -> listViewModel.muteChannel(action.channel)
            is UnmuteChannel -> listViewModel.unmuteChannel(action.channel)
            is PinChannel -> listViewModel.pinChannel(action.channel)
            is UnpinChannel -> listViewModel.unpinChannel(action.channel)
            is ArchiveChannel -> listViewModel.archiveChannel(action.channel)
            is UnarchiveChannel -> listViewModel.unarchiveChannel(action.channel)
            else -> listViewModel.executeOrConfirm(action)
        }
    },
    onDismiss = { listViewModel.dismissChannelAction() },
)

For destructive actions like LeaveGroup and DeleteConversation, you should show a confirmation dialog before executing the action. Here's how to handle them:

val activeAction = listViewModel.activeChannelAction

if (activeAction is LeaveGroup) {
    SimpleDialog(
        modifier = Modifier.padding(16.dp),
        title = "Leave group",
        message = "Are you sure you want to leave this group?",
        onPositiveAction = { listViewModel.leaveGroup(activeAction.channel) },
        onDismiss = { listViewModel.dismissChannelAction() },
    )
} else if (activeAction is DeleteConversation) {
    SimpleDialog(
        modifier = Modifier.padding(16.dp),
        title = "Delete conversation",
        message = "Are you sure you want to delete this conversation?",
        onPositiveAction = { listViewModel.deleteConversation(activeAction.channel) },
        onDismiss = { listViewModel.dismissChannelAction() },
    )
}

The executeOrConfirm() method either runs the action immediately or stores it in activeChannelAction for destructive actions, which you can observe to show the appropriate confirmation dialog.

Customization

The ChannelActionsSheet API surface is deliberately small:

@Composable
fun ChannelActionsSheet(
    channel: Channel,
    actions: List<ChannelAction>,
    onActionClick: (ChannelAction) -> Unit,
    onDismiss: () -> Unit,
    modifier: Modifier = Modifier,
    currentUser: User? = null,
)

Visual customization — corner radius, scrim color, drag handle, container color — is not taken via parameters. The sheet always uses the Stream design tokens (32dp top corners, backgroundCoreElevation1 container, backgroundCoreScrim scrim, the Material 3 drag handle) so every modal bottom sheet across the SDK stays visually consistent.

To customize the content of the sheet (the header row or the actions list), override the relevant factory methods on ChatComponentFactory:

  • ChatComponentFactory.ChannelMenuHeaderContent(params: ChannelMenuHeaderContentParams) — owns the header row (avatar, channel name, member status, state icons).
  • ChatComponentFactory.ChannelMenuCenterContent(params: ChannelMenuCenterContentParams) — owns the list of actions rendered below the header.

For example, to replace the header with a custom composable for every ChannelActionsSheet instance in your app:

ChatTheme(
    componentFactory = object : ChatComponentFactory {
        @Composable
        override fun ChannelMenuHeaderContent(params: ChannelMenuHeaderContentParams) {
            // Your custom header here.
        }
    },
) {
    // Your UI
}

The factory override propagates to every sheet that uses the default header — including the one rendered by ChannelsScreen — without your custom UI needing to know which sheet variant is in play.