Selecting Channels

The SelectedChannelMenu 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 options: Actions like "View Info", "Leave Group", "Delete Conversation", or "Mute Channel" based on user permissions.

Let's see how to use the SelectedChannelMenu in your code.

Usage

If you're using the ChannelScreen component, you don't have to do anything. The SelectedChannelMenu component and its logic will be integrated into the UI.

If you're looking to build a custom UI, you can add the SelectedChannelMenu component on top of your UI, like so:

val listViewModel: ChannelListViewModel by viewModels { ChannelViewModelFactory() }

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

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

            Box(modifier = Modifier.fillMaxSize()) {
                // The rest of your UI

                if (currentlySelectedChannel != null) {
                    val isMuted = listViewModel.isChannelMuted(currentlySelectedChannel.cid)

                    SelectedChannelMenu(
                        modifier = Modifier
                            .fillMaxWidth() // Fill width
                            .wrapContentHeight() // Wrap height
                            .align(Alignment.BottomCenter), // Aligning the content to the bottom
                        selectedChannel = currentlySelectedChannel,
                        isMuted = isMuted,
                        currentUser = user,
                        onChannelOptionClick = { listViewModel.performChannelAction(it) },
                        onDismiss = { listViewModel.dismissChannelAction() }
                    )
                }
            }
        }
    }
}

For the SelectedChannelMenu component to work, you need to provide selectedChannel, isMuted and currentUser parameters.

In the example above, you fetch the data from a ChannelListViewModel that you use in the rest of the UI. But you can also provide the data manually if you decide not to use our components.

Notice how you also show the SelectedChannelMenu only if the selectedChannel is not null. This is a smart way of knowing when to show the info and when to hide it.

With a bit of extra code for the rest of the content, when selecting a channel, the snippet above will produce the next UI:

The SelectedChannelMenu Component

This represents just the SelectedChannelMenu component, the rest of the UI can be whatever your implementation requires.

Finally, you can see a list of ChannelOptions, which are different depending on whether you're an admin for this channel or just a member. Clicking these will trigger channel actions.

Let's see how to handle these.

Handling Actions

The SelectedChannelMenu exposes two required action handlers:

@Composable
fun SelectedChannelMenu(
    ..., // State & styling
    onChannelOptionClick: (ChannelAction) -> Unit,
    onDismiss: () -> Unit,
)
  • onChannelOptionClick: 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
    • Cancel - Dismiss the menu
  • onDismiss: Called when the menu is dismissed (e.g., by tapping the overlay).

Here's how ChannelsScreen handles these actions internally:

SelectedChannelMenu(
    ..., // State and styling
    onChannelOptionClick = { 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.performChannelAction(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 performChannelAction() method stores the action in activeChannelAction, which you can observe to show the appropriate confirmation dialog.

Customization

The SelectedChannelMenu offers the following customization options:

@Composable
fun SelectedChannelMenu(
    ..., // State and actions
    modifier: Modifier = Modifier,
    channelOptions: List<ChannelOptionState> = buildDefaultChannelOptionsState(...),
    shape: Shape = ChatTheme.shapes.bottomSheet,
    overlayColor: Color = ChatTheme.colors.overlay,
    headerContent: @Composable ColumnScope.() -> Unit = { /* Default header */ },
    centerContent: @Composable ColumnScope.() -> Unit = { /* Default options */ },
)
  • modifier: Modifier for the root component. Useful for size, padding, and alignment.
  • channelOptions: The list of options shown in the menu. By default, uses buildDefaultChannelOptionsState() which builds options based on user permissions (ownCapabilities).
  • shape: Shape of the menu card. Defaults to ChatTheme.shapes.bottomSheet (rounded top corners). Change to RoundedCornerShape for a dialog style.
  • overlayColor: Color of the background overlay. Defaults to ChatTheme.colors.overlay.
  • headerContent: Content at the top of the menu. By default, shows channel name, member status text, and member avatars.
  • centerContent: Content in the center of the menu. By default, shows the list of channel options.

Here's an example of customizing this component to imitate a dialog:

SelectedChannelMenu(
    modifier = Modifier
        .padding(16.dp) // Adding padding to the component
        .fillMaxWidth() // Fill width
        .wrapContentHeight() // Wrap height
        .align(Alignment.Center), // Centering the component
    shape = RoundedCornerShape(16.dp), // Rounded corners for all sides
    ... // State
)

This code will produce the following UI:

The SelectedChannelMenu Component

The SelectedChannelMenu component now looks more like a dialog that displays over other elements. This is just an example of how easy it is to apply UI customization to our components.