Input area
MessageInputConfig configures the input area used by ChatPage. The default input supports text, voice recording, emoji, extension plugins, reference previews, drafts, typing status, and mention callbacks. It sends through the active ChatProvider and BaseChannel.
Configure controls
Use toolbarControls to choose the visible buttons, and use the boolean flags to enable or disable each built-in mode.
ChatPage(
channel: channel,
config: ChatPageConfig(
inputConfig: MessageInputConfig(
toolbarControls: const [
MessageInputBarControl.voice,
MessageInputBarControl.emoji,
MessageInputBarControl.extension,
],
enableVoiceInput: true,
enableEmojiPanel: true,
enableExtensionPanel: true,
),
),
);
On Android, the built-in input uses one trailing slot for extension and send. Empty draft shows the extension + button; non-empty draft switches the same slot to send. Emoji stays independent, and voice stays on the leading side.
Extension plugins
The default extension panel includes photo, video, camera, filming, and file entries. Location is available when your app supplies a locationResolver or onTap.
MessageInputConfig(
extensionPlugins: const [
MessageInputExtensionPlugin.photo(),
MessageInputExtensionPlugin.video(),
MessageInputExtensionPlugin.file(),
],
);
Provide media or location values from your own picker when you do not want the default picker flow.
MessageInputConfig(
extensionPlugins: [
MessageInputExtensionPlugin.photo(
mediaPathResolver: (context, plugin) async {
return selectedImagePath;
},
),
MessageInputExtensionPlugin.location(
locationResolver: (context, plugin) async {
return MessageInputLocationDraft(
longitude: 103.8198,
latitude: 1.3521,
poiName: 'Singapore',
thumbnailPath: localThumbnailPath,
);
},
),
],
);
Permissions
Use onPermissionRequest to connect the input panel to your app permission flow.
MessageInputConfig(
onPermissionRequest: (context, plugin) async {
return true;
},
);
Permission resolution order is:
| Priority | API | Scope |
|---|---|---|
| 1 | MessageInputExtensionPlugin.permissionRequest | One plugin entry, such as camera or file. |
| 2 | MessageInputConfig.onPermissionRequest | Shared fallback for input extension entries. |
| 3 | Built-in media permission helper | Used by the default media picker flow for photo, video, camera, and filming. |
The built-in helper is scoped by plugin type: photo and video request media-library access; camera requests camera only; direct Android filming requests camera and microphone; non-direct filming keeps the media-library step when the picker flow needs it.
Return false when the user denies permission or cancels the request. The input panel stops the action without sending a message. For host-provided pickers, return null from the resolver when the user cancels selection.
Mentions
Mentions require your app to provide candidates for the current BaseChannel. Use mentionResolver for an inline candidate list, or mentionPicker to open a full picker.
MessageInputConfig(
enableMention: true,
mentionResolver: (context, channel) async {
return const [
MessageInputMentionCandidate(userId: 'user_1', displayName: 'Alex'),
MessageInputMentionCandidate(userId: 'user_2', displayName: 'Taylor'),
];
},
mentionPicker: (context, channel) async {
return const MessageInputMentionCandidate(
userId: 'user_1',
displayName: 'Alex',
);
},
);
Voice recording
The default voice recorder uses MessageInputDefaultVoiceRecorder and sends HDVoiceMessage through ChatProvider.sendVoiceMessage. You can replace the recorder or change the recording limits.
MessageInputConfig(
voiceRecorder: MyVoiceRecorder(),
minimumVoiceRecordingDuration: const Duration(seconds: 1),
maximumVoiceRecordingDuration: const Duration(seconds: 60),
);
Reference preview
When the user replies to a message, ChatPage passes the reference message to the input area. Customize the preview with referencePreviewConfig.
MessageInputConfig(
referencePreviewConfig: MessageInputReferencePreviewConfig(
backgroundColor: const Color(0xFFF4F6F8),
builder: (context, message, senderName, summary, onClose) {
return ListTile(
dense: true,
title: Text(senderName),
subtitle: Text(summary),
trailing: IconButton(
icon: const Icon(Icons.close),
onPressed: onClose,
),
);
},
),
);