# Message Moderation

Stream Chat can moderate messages before they are delivered (bounce) and lets users report messages to moderators (flag). This page shows how to customize the Compose components for moderated messages. For how moderated messages are represented in the SDK, see the [Moderation](https://getstream.io/chat/docs/sdk/android/guides/moderation/) guide.

## Bounced Messages

A bounced message stays in the `MessageList` as a regular message item. Long pressing it sets the selected message state to `SelectedMessageFailedModerationState`, and `ChannelScreen` then shows a `ModeratedMessageDialog` with the **Send anyway**, **Edit message** and **Delete message** options. Tapping it does nothing; the dialog opens on long press only.

The options are `ModeratedMessageOption` instances:

| Option                   | Result                                                                                                     |
| ------------------------ | ---------------------------------------------------------------------------------------------------------- |
| `SendAnyway`             | Sends the message again through `MessageListViewModel.performMessageAction(Resend(message))`.              |
| `EditMessage`            | Puts the message into the composer through `MessageComposerViewModel.performMessageAction(Edit(message))`. |
| `DeleteMessage`          | Deletes the local copy through `MessageListViewModel.deleteMessage(message, hard = true)`.                 |
| `CustomModerationOption` | Your own option. The SDK passes it to your handler and does nothing else.                                  |

### Customizing the Dialog

`ModeratedMessageDialog` is a public composable with slots for its title, description and options, so you can change any part of it. Pass a `modifier` that paints a background: the dialog content is a bare `Column`, so without one it renders transparently over the scrim.

```kotlin
ModeratedMessageDialog(
    message = message,
    modifier = Modifier.background(
        shape = MaterialTheme.shapes.medium,
        color = ChatTheme.colors.backgroundCoreSurfaceDefault,
    ),
    onDismissRequest = { listViewModel.removeOverlay() },
    onDialogOptionInteraction = { message, option -> /* Handle the option */ },
    // Do not allow sending a bounced message anyway.
    moderatedMessageOptions = listOf(EditMessage, DeleteMessage),
    dialogTitle = {
        Text(
            text = "Message not sent",
            style = ChatTheme.typography.headingMedium,
            color = ChatTheme.colors.textPrimary,
        )
    },
    dialogDescription = {
        Text(
            text = "This message goes against our community guidelines.",
            style = ChatTheme.typography.bodyDefault,
            color = ChatTheme.colors.textSecondary,
        )
    },
)
```

To add your own option, use `CustomModerationOption` and handle it in `onDialogOptionInteraction`:

```kotlin
val reportOption = CustomModerationOption(text = R.string.report_to_support)

ModeratedMessageDialog(
    message = message,
    modifier = Modifier.background(
        shape = MaterialTheme.shapes.medium,
        color = ChatTheme.colors.backgroundCoreSurfaceDefault,
    ),
    onDismissRequest = { listViewModel.removeOverlay() },
    moderatedMessageOptions = listOf(SendAnyway, EditMessage, DeleteMessage, reportOption),
    onDialogOptionInteraction = { message, option ->
        when (option) {
            is SendAnyway -> listViewModel.performMessageAction(Resend(message))
            is EditMessage -> composerViewModel.performMessageAction(Edit(message))
            is DeleteMessage -> listViewModel.deleteMessage(message = message, hard = true)
            is CustomModerationOption -> reportToSupport(message)
        }
    },
)
```

If you only need to change how a single option row looks, pass an `itemContent` to `ModeratedMessageDialogOptions`, which is the composable behind the `dialogOptions` slot. `ModeratedMessageOptionItem` renders the default row.

>
> **Note:** `ChannelScreen` builds the dialog itself and does not expose it as a slot. To use a customized `ModeratedMessageDialog`, build your own screen around `MessageList` and render the dialog when `listViewModel.currentMessagesState.value.selectedMessageState` is a `SelectedMessageFailedModerationState`. See [Custom Message List](https://getstream.io/chat/docs/sdk/android/compose-cookbook/custom-message-list/).
>

## Blocked and Removed Messages

Moderation can also block or remove a message outright instead of bouncing it. `MessageListController` turns every error message that is **not** a bounce into a `ModeratedMessageItemState`, which the list renders through `ChatComponentFactory.MessageListModeratedItemContent` rather than as a regular message:

```kotlin
class CustomChatComponentFactory : ChatComponentFactory {

    @Composable
    override fun LazyItemScope.MessageListModeratedItemContent(
        params: MessageListModeratedItemContentParams,
    ) {
        val message = params.moderatedMessageItem.message
        Text(
            modifier = Modifier.fillMaxWidth().padding(8.dp),
            text = message.moderation?.originalText ?: message.text,
            color = ChatTheme.colors.textSecondary,
        )
    }
}

ChatTheme(
    componentFactory = CustomChatComponentFactory(),
) {
    ChannelScreen(viewModelFactory = viewModelFactory)
}
```

>
> **Note:** This hook does not affect bounced messages, which stay regular message items. Customize those through the dialog above.
>

## Flag Action

The **Flag message** action is added to the message options overlay for messages from other users, when the user has the `flag-message` channel capability. Selecting it shows a confirmation dialog and then flags the message without a reason.

To hide the action, set `isFlagMessageVisible` to `false` in the message actions configuration:

```kotlin
ChatTheme(
    config = ChatUiConfig(
        messageActions = MessageActionsConfig(
            optionsVisibility = MessageActionsOptionsVisibility(
                isFlagMessageVisible = false,
            ),
        ),
    ),
) {
    ChannelScreen(viewModelFactory = viewModelFactory)
}
```

Flagging with a reason needs your own screen, because `ChannelScreen` always shows its own confirmation dialog and then calls `flagMessage` with `reason = null`. On a custom screen, handle the `Flag` action and call the view model, which accepts a reason:

```kotlin
listViewModel.flagMessage(
    message = message,
    reason = "inappropriate",
    customData = emptyMap(),
)
```

For more details about message options, see [Message Actions](https://getstream.io/chat/docs/sdk/android/compose/message-components/message-actions/).

## Changing the Texts

If you only need to change the texts, you don't have to override any component. Redefine the following keys in your app's `strings.xml`:

| Key                                       | Default value                                                                                                                       |
| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `stream_ui_moderation_dialog_title`       | Failed to send message                                                                                                              |
| `stream_ui_moderation_dialog_description` | This message might violate our moderation policy. Are you sure you want to send it? Please take a look at our Community Guidelines. |
| `stream_ui_moderation_dialog_send`        | Send anyway                                                                                                                         |
| `stream_ui_moderation_dialog_edit`        | Edit message                                                                                                                        |
| `stream_ui_moderation_dialog_delete`      | Delete message                                                                                                                      |
| `stream_compose_flag_message`             | Flag Message                                                                                                                        |
| `stream_compose_flag_message_title`       | Flag Message                                                                                                                        |
| `stream_compose_flag_message_text`        | Do you want to send a copy of this message to a moderator for further investigation?                                                |

See the [localization guide](https://getstream.io/chat/docs/sdk/android/compose/guides/custom-translations/) for more details.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/android/compose/message-components/message-moderation/](https://getstream.io/chat/docs/sdk/android/compose/message-components/message-moderation/).