Message menu for PubNub Chat Components for Android
MessageMenu
allows you to perform actions on sent messages by long-tapping them. It allows you, for example, to copy a message to the clipboard.
MessageMenu
uses the concept of Bottom Sheets from Material Design. By default, the Copy message
option in MessageMenu
is enabled, but you can easily disable or override it. MessageMenu
appears on BottomMenu
which pops up when you long-tap a message. A single message option available on MessageMenu
consists of an icon and a text field that provides the name of the option.
BottomMenu
MessageMenu
is defined in PubNub Chat Components for Android as part of the BottomMenu
composable function:
@Composable
fun BottomMenu(
message: MessageUi.Data?,
onAction: (MenuAction) -> Unit,
onDismiss: () -> Unit,
modifier: Modifier = Modifier,
visible: Boolean = true,
states: List<MenuItemState> = message?.let { MenuDefaults.items(message) } ?: emptyList(),
headerContent: @Composable ColumnScope.() -> Unit = {},
bodyContent: @Composable ColumnScope.() -> Unit = {
MessageMenu(items = states, onClick = { onAction(it) })
},
)
Parameter | Description |
---|---|
message | Reference to the selected message, needed for all action types |
onAction | Global onClick handler with MenuAction passed |
onDismiss | Action to call after closing or clicking outside of MessageMenu |
modifier | Way in which MessageMenu is drawn |
visible | Visibility state |
states | List of menu items |
headerContent | Rendering function for the header, empty by default, used for drawing message reactions |
bodyContent | Rendering function for the body, used for MessageMenu by default |
BottomMenu
describes the bottom drawer that pops up on the screen when you long-tap a sent message. The layout of BottomMenu
itself is split into headerContent
and bodyContent
, both of which are modifiable. By default, headerContent
is empty and must be used to pass message reactions while bodyContent
contains the MessageMenu
component which specifies options available in BottomMenu
:
@Composable
fun MessageMenu(
items: List<MenuItemState>,
onClick: (MenuAction) -> Unit,
modifier: Modifier = Modifier,
content: @Composable ColumnScope.(MenuItemState) -> Unit = { state ->
MenuItem(
state = state,
onClick = onClick,
)
}
)
Check out a sample usage:
@Composable
fun MessageMenuExample() {
val items = listOf(
MenuItemState(
title = R.string.menu_copy,
iconPainter = rememberVectorPainter(image = Icons.Rounded.ContentCopy),
action = Copy(dummyMessageData()),
),
MenuItemState(
title = R.string.menu_delete,
iconPainter = rememberVectorPainter(image = Icons.Rounded.Delete),
action = Delete(dummyMessageData()),
),
)
MenuItemTheme(DefaultMenuItemTheme) {
show all 18 linesThe MessageMenu
composable function consists of:
items
items
stands for the list of menu items referred to in the MenuItemState
class. This call contains a string resource reference to the title
of the menu option, iconPainter
for drawing an icon (you can use either local or remote files), and the type of action
on which the option is triggered (set to onClick
by default).
data class MenuItemState(
@StringRes val title: Int,
val iconPainter: Painter,
val action: MenuAction,
)
Default values for those parameters are stored in MenuDefaults
that contains both the default MessageMenu
options and message reactions:
object MenuDefaults {
@Composable
fun items(message: MessageUi.Data) = listOf(
MenuItemState(
title = R.string.menu_copy,
iconPainter = rememberVectorPainter(image = Icons.Rounded.ContentCopy),
action = Copy(message),
),
)
onClick
onClick
stands for the callback handler for selecting an action. It calls MenuAction
which is a sealed class for defining multiple actions.
sealed class MenuAction(val message: MessageUi.Data)
class Copy(message: MessageUi.Data) : MenuAction(message)
modifier
modifier
is an obligatory Compose parameter that defines how the component is drawn. We can use it to define the animation for BottomMenu
, its position, or its size.
content
content
is a composable function used for rendering a single item (MenuItem
).
Check out a sample usage:
@Composable
fun MenuItemExample() {
val message: MessageUi.Data = dummyMessageData()
MenuItemTheme(DefaultMenuItemTheme) {
MenuItem(
MenuItemState(
title = R.string.menu_copy,
iconPainter = rememberVectorPainter(image = Icons.Rounded.ContentCopy),
action = Copy(message),
)
)
}
}
Disable message options
MessageMenu
with the Copy message
option is enabled by default. To disable it, replace the bodyContent
parameter with an empty function:
BottomMenu(
onAction = { action ->
onAction(action)
},
onDismiss = onDismiss,
visible = visible && message != null,
modifier = modifier,
// Add the below line
bodyContent = {},
)
Override message options
You can define your own message options to be displayed in MessageMenu
or replace the existing option with a different one. To add additional options:
-
Define a new
MessageMenu
option, such asDelete
:val menuItems = listOf(
MenuItemState(
title = R.string.menu_copy,
iconPainter = rememberVectorPainter(image = Icons.Rounded.ContentCopy),
action = Copy(message),
),
MenuItemState(
title = R.string.menu_delete,
iconPainter = rememberVectorPainter(image = Icons.Rounded.Delete),
action = Delete(message),
)
) -
Pass
menuItems
in thestates
parameter toBottomMenu
in your app code:BottomMenu(
…
states = menuItems,
…
) -
Consume the new option by overriding the
onAction
parameter and adding the additionalDelete
class:onAction = { action ->
when (action) {
is Copy -> {
action.message.text?.let { content ->
messageViewModel.copy(AnnotatedString(content))
}
}
is Delete -> handleDelete(action.customData, action.message)
is React -> reactionViewModel.reactionSelected(action)
else -> {}
}
}
Theming
As already mentioned, a single MessageMenu
option (like Copy message
) is defined by the MenuItem
composable function that applies the theming outlined in MenuItemTheme
.
class MenuItemTheme(
modifier: Modifier = Modifier,
text: TextTheme,
icon: IconTheme,
horizontalArrangement: Arrangement.Horizontal,
verticalAlignment: Alignment.Vertical,
)
Its default implementation looks as follows:
@Composable
fun MenuItem(
state: MenuItemState,
onClick: ((MenuAction) -> Unit)? = null,
theme: MenuItemTheme = LocalMenuItemTheme.current,
) {
Row(
modifier = Modifier
.clickable(
onClick = { onClick?.invoke(state.action) },
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
)
.then(theme.modifier),
horizontalArrangement = theme.horizontalArrangement,
show all 31 linesCustomize parameters
MenuItemTheme
allows you to customize these parameters for MessageMenu
:
- Background color of the whole list and a single option
- Text color of an option title
- Icon tint
Background color of the list
To change the background color of the whole option list to a different one, pass the modifier
parameter with a selected background color like Red
:
BottomMenu(
onAction = { action ->
onAction(action)
onDismiss()
},
onDismiss = onDismiss,
visible = visible && message != null,
// Add the below line
modifier = Modifier.background(Color.Red),
...
)
Since in the default implementation there's no padding set to modifier
, add extra padding to show the difference better:
modifier = Modifier
.background(Color.Red)
.padding(12.dp),
Background color of an option
To change the background color of a single option, override the background
parameter to a selected color like DarkGray
:
val theme = ThemeDefaults.menuItem(modifier = Modifier.fillMaxWidth().background(Color.DarkGray))
MenuItemTheme(theme) {
BottomMenu(...)
}
Color of the option title
To change the color of an option title to a different one, override the menuItemTitle
parameter to a selected color like Red
:
val theme = ThemeDefaults.menuItem(text = TextThemeDefaults.menuItemTitle(color = Color.Red))
MenuItemTheme(theme) {
BottomMenu(...)
}
Icon tint
To change the color tint of an icon to a different one, override the menuIcon
parameter to a selected color like Red
:
val theme = ThemeDefaults.menuItem(icon = IconThemeDefaults.menuIcon(tint = Color.Red))
MenuItemTheme(theme) {
BottomMenu(...)
}