Skip to main content

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.

Dart
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.

Dart
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.

Dart
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.

Dart
MessageInputConfig(
onPermissionRequest: (context, plugin) async {
return true;
},
);

Permission resolution order is:

PriorityAPIScope
1MessageInputExtensionPlugin.permissionRequestOne plugin entry, such as camera or file.
2MessageInputConfig.onPermissionRequestShared fallback for input extension entries.
3Built-in media permission helperUsed 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.

Dart
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.

Dart
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.

Dart
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,
),
);
},
),
);