@Composable
fun CustomMessageListHeader(cid: String?, onBackClick: () -> Unit = {})
Custom Header for Message List
The message list header sits above the list of messages and shows the channel name and avatar, members and online members count, current connection status, and a back button. It also allows customization through its parameters.
In this example we will customize the MessageListHeader
component and make it look similar to the WhatsApp conversation title bar.
State Handling
Let’s define a new composable, CustomMessageListHeader
, that takes the channel cid
and a back handler as parameters.
Inside it we’ll use the built-in MessageListViewModel
to acquire the state that we need.
val viewModel = viewModel(
modelClass = MessageListViewModel::class.java,
factory = MessagesViewModelFactory(LocalContext.current, channelId = cid)
)
val channel = viewModel.channel
val connectionState by viewModel.connectionState.collectAsStateWithLifecycle()
val currentUser by viewModel.user.collectAsStateWithLifecycle()
We pass channel
, connectionState
and currentUser
to the MessageListHeader
component, alongside other state that we get from the view model:
MessageListHeader(
channel = channel,
currentUser = currentUser,
connectionState = connectionState,
typingUsers = viewModel.typingUsers,
messageMode = viewModel.messageMode,
onBackPressed = onBackClick,
)
Leading Content
Let’s customize the leading content, which represents the start slot of the header. This is a very simple customization: we just replace the default back arrow with our custom one.
Note that we’ll also use CustomHeaderButton
for other buttons that we’ll add later to the header.
MessageListHeader(
// State
leadingContent = { CustomHeaderLeadingContent(onClick = onBackClick) },
)
@Composable
private fun CustomHeaderLeadingContent(onClick: () -> Unit) {
CustomHeaderButton(
iconRes = R.drawable.ic_back,
contentDescription = "Back",
onClick = onClick
)
}
@Composable
private fun CustomHeaderButton(
@DrawableRes iconRes: Int,
contentDescription: String,
onClick: () -> Unit,
) {
IconButton(
onClick = onClick,
content = {
Icon(
painter = painterResource(id = iconRes),
contentDescription = contentDescription,
tint = Color.White,
)
}
)
}
Center Content
Now, let’s use the middle slot of the header, named centerContent
, and pass our CustomHeaderCenterContent
to it.
We use several composables and utility methods in order to display the channel avatar, name, member and online member count:
- The
ChannelAvatar
component from our SDK to show the avatar. Based on the state of the channel and the number of members, it shows different types of images. - The
ChatTheme.ChannelNameFormatter.formatChannelName
method to show the name of the channel, based on a set of rules. Search for theDefaultChannelNameFormatter
component for more info. - The
channel.getMembersStatusText
extension method to show either a member count for a group channel or the last seen text for a direct one-to-one conversation.
MessageListHeader(
// State
leadingContent = { CustomHeaderLeadingContent(onClick = onBackClick) },
centerContent = { CustomHeaderCenterContent(channel = channel, currentUser = currentUser) },
)
@Composable
private fun CustomHeaderCenterContent(channel: Channel, currentUser: User?) {
Row {
ChannelAvatar(
modifier = Modifier.size(40.dp),
channel = channel,
currentUser = currentUser,
)
Spacer(modifier = Modifier.size(10.dp))
Column {
Text(
text = ChatTheme.channelNameFormatter.formatChannelName(channel, currentUser),
color = Color.White,
fontWeight = FontWeight.SemiBold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
text = channel.getMembersStatusText(LocalContext.current, currentUser),
color = Color.LightGray,
style = ChatTheme.typography.footnote
)
}
}
}
Trailing Content
Next, we’ll use the last slot, trailingContent
, to add video and audio call buttons and a menu button, like WhatsApp has. As this is only an example, these buttons don’t do anything. You can check out our Video SDK to implement audio & video calling.
MessageListHeader(
// State
leadingContent = { CustomHeaderLeadingContent(onClick = onBackClick) },
centerContent = { CustomHeaderCenterContent(channel = channel, currentUser = currentUser) },
trailingContent = { CustomHeaderTrailingContent() },
)
@Composable
private fun CustomHeaderTrailingContent() {
Row(
modifier = Modifier
.fillMaxWidth()
.offset(x = 10.dp),
horizontalArrangement = Arrangement.End
) {
CustomHeaderButton(
iconRes = R.drawable.ic_videocam,
contentDescription = "Video Call",
onClick = {}
)
CustomHeaderButton(
iconRes = R.drawable.ic_phone,
contentDescription = "Audio Call",
onClick = {}
)
CustomHeaderButton(
iconRes = R.drawable.ic_menu,
contentDescription = "Menu",
onClick = {}
)
}
}
Final Touches
As final touches, we change the height and the background color of the header:
MessageListHeader(
// State
modifier = Modifier.height(55.dp),
color = Color(0xFF0F7B6F),
// Content slots
)
Make sure to use ChatTheme
as the root of all the composables that use our Compose UI Components. It’s used to provide default style properties and utility methods.
Full code
Below you can find the full code for our implementation.
@Composable
fun CustomMessageListHeader(cid: String?, onBackClick: () -> Unit = {}) {
cid?.let {
val viewModel = viewModel(
modelClass = MessageListViewModel::class.java,
factory = MessagesViewModelFactory(LocalContext.current, channelId = cid)
)
val channel = viewModel.channel
val connectionState by viewModel.connectionState.collectAsStateWithLifecycle()
val currentUser by viewModel.user.collectAsStateWithLifecycle()
MessageListHeader(
channel = channel,
currentUser = currentUser,
connectionState = connectionState,
modifier = Modifier.height(55.dp),
typingUsers = viewModel.typingUsers,
messageMode = viewModel.messageMode,
onBackPressed = onBackClick,
color = Color(0xFF0F7B6F),
leadingContent = { CustomHeaderLeadingContent(onClick = onBackClick) },
centerContent = { CustomHeaderCenterContent(channel = channel, currentUser = currentUser) },
trailingContent = { CustomHeaderTrailingContent() },
)
}
}
@Composable
private fun CustomHeaderLeadingContent(onClick: () -> Unit) {
CustomHeaderButton(
iconRes = R.drawable.ic_back,
contentDescription = "Back",
onClick = onClick
)
}
@Composable
private fun CustomHeaderCenterContent(channel: Channel, currentUser: User?) {
Row {
ChannelAvatar(
modifier = Modifier.size(40.dp),
channel = channel,
currentUser = currentUser,
)
Spacer(modifier = Modifier.size(10.dp))
Column {
Text(
text = ChatTheme.channelNameFormatter.formatChannelName(channel, currentUser),
color = Color.White,
fontWeight = FontWeight.SemiBold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
text = channel.getMembersStatusText(LocalContext.current, currentUser),
color = Color.LightGray,
style = ChatTheme.typography.footnote
)
}
}
}
@Composable
private fun CustomHeaderTrailingContent() {
Row(
modifier = Modifier
.fillMaxWidth()
.offset(x = 10.dp),
horizontalArrangement = Arrangement.End
) {
CustomHeaderButton(
iconRes = R.drawable.ic_videocam,
contentDescription = "Video Call",
onClick = {}
)
CustomHeaderButton(
iconRes = R.drawable.ic_phone,
contentDescription = "Audio Call",
onClick = {}
)
CustomHeaderButton(
iconRes = R.drawable.ic_menu,
contentDescription = "Menu",
onClick = {}
)
}
}
@Composable
private fun CustomHeaderButton(@DrawableRes iconRes: Int, contentDescription: String, onClick: () -> Unit) {
IconButton(
onClick = onClick,
content = {
Icon(
painter = painterResource(id = iconRes),
contentDescription = contentDescription,
tint = Color.White,
)
}
)
}