Shortcut Formatter
Introduction
The ShortcutFormatter
class extends the CometChatTextFormatter
class to provide a mechanism for handling shortcuts within messages. This guide will walk you through the process of using ShortcutFormatter to implement shortcut extensions in your CometChat application.
Setup
- Create the ShortcutFormatter Class: Define the
ShortcutFormatter
class by extending theCometChatTextFormatter
class.
- Dart
import 'package:cometchat_chat_uikit/cometchat_chat_uikit.dart';
class ShortcutFormatter extends CometChatTextFormatter {
}
- Init: Initialize the
messageShortcuts
map andshortcuts
list in the init().
- Dart
void init() {
trackingCharacter ??= "!";
}
- Prepare Shortcuts: Implement the
prepareShortcuts()
method to fetch shortcuts from the server using CometChat extension.
- Dart
bool isShortcutTracking = false;
void prepareShortcuts(TextEditingController textEditingController) {
CometChat.callExtension('message-shortcuts', 'GET', '/v1/fetch', null,
onSuccess: (map) {
if (map.isNotEmpty) {
Map<String, dynamic> data = map["data"];
if (data.isNotEmpty) {
Map<String, dynamic> shortcuts = data["shortcuts"];
if (shortcuts.isNotEmpty) {
parseData(
shortcuts: shortcuts,
textEditingController: textEditingController
);
}
}
}
}, onError: (error) {});
}
void parseData({Map<String, dynamic>? shortcuts, required TextEditingController textEditingController}) async {
if (shortcuts == null || shortcuts.isEmpty) {
suggestionListEventSink?.add([]);
if (onSearch != null) {
onSearch!(null);
}
CometChatUIEvents.hidePanel(composerId, CustomUIPosition.composerPreview);
} else {
CometChatUIEvents.hidePanel(composerId, CustomUIPosition.composerPreview);
if (suggestionListEventSink != null && shortcuts.isNotEmpty) {
List<SuggestionListItem> list = [];
shortcuts.forEach((key, value) => list.add(SuggestionListItem(
id: key,
title: "$key → $value",
onTap: () {
int cursorPos = textEditingController.selection.base.offset;
String textOnLeftOfValue = textEditingController.text.substring(0, cursorPos - 1);
String textOnRightOfValue = textEditingController.text.substring(cursorPos);
textEditingController.text = "$textOnLeftOfValue$value $textOnRightOfValue";
updatePreviousText(textEditingController.text);
textEditingController.selection = TextSelection(
baseOffset: cursorPos - 1 + "$value".length + 1,
extentOffset: cursorPos - 1 + "$value".length + 1,
);
resetMatchTracker();
isShortcutTracking = false;
CometChatUIEvents.hidePanel(
composerId, CustomUIPosition.composerPreview
);
})
));
suggestionListEventSink?.add(list);
}
}
}
void updatePreviousText(String text) {
if (previousTextEventSink != null) {
previousTextEventSink!.add(text);
}
}
void resetMatchTracker() {
suggestionListEventSink?.add([]);
if (onSearch != null) {
onSearch!(null);
}
}
- Override onChange Method: Override the
onChange()
method to search for shortcuts based on the entered query.
- Dart
void onChange(TextEditingController textEditingController, String previousText) {
var cursorPosition = textEditingController.selection.base.offset;
if (textEditingController.text.isEmpty) {
resetMatchTracker();
if (previousText.length > textEditingController.text.length) {
if (previousText[cursorPosition] == trackingCharacter && isShortcutTracking) {
isShortcutTracking = false;
if (onSearch != null) {
onSearch!(null);
}
CometChatUIEvents.hidePanel(composerId, CustomUIPosition.composerPreview);
}
return;
}
}
if (previousText.length > textEditingController.text.length) {
if (previousText[cursorPosition] == trackingCharacter) {
isShortcutTracking = false;
if (onSearch != null) {
onSearch!(null);
}
CometChatUIEvents.hidePanel(composerId, CustomUIPosition.composerPreview);
}
} else {
String previousCharacter = cursorPosition == 0 ? "" : textEditingController.text[cursorPosition - 1];
bool isSpace = cursorPosition == 1 || (textEditingController.text.length > 1 && cursorPosition > 1 && (textEditingController.text[cursorPosition - 2] == " " || textEditingController.text[cursorPosition - 2] == "\n"));
if (isShortcutTracking) {
isShortcutTracking = false;
if (onSearch != null) {
onSearch!(null);
}
CometChatUIEvents.hidePanel(composerId, CustomUIPosition.composerPreview);
} else if (previousCharacter == trackingCharacter && isSpace) {
isShortcutTracking = true;
if (onSearch != null) {
onSearch!(trackingCharacter);
}
CometChatUIEvents.showPanel(composerId, CustomUIPosition.composerPreview, (context) => getLoadingIndicator(context, cometChatTheme));
prepareShortcuts(textEditingController);
}
}
}
- Handle Scroll to Bottom: Override the
onScrollToBottom()
method if needed.
- Dart
void onScrollToBottom(TextEditingController textEditingController) {
// TODO: implement onScrollToBottom
}
Usage
The widgets CometChatConversations, CometChatMessageComposer and CometChatMessageList have a property called textFormatters
which accepts a list of CometChatTextFormatter to format the text. The code shared below textFormatters
consisting of the above created Shortcuts formatter being passed down to CometChatMessageComposer from CometChatConversationsWithMessages with help of configurations.
- Dart
import 'package:cometchat_chat_uikit/cometchat_chat_uikit.dart';
import 'package:flutter/material.dart';
import 'shortcut_formatter.dart';
class ShortcutFormatterExample extends StatefulWidget {
const ShortcutFormatterExample({super.key});
State<ShortcutFormatterExample> createState() => _ShortcutFormatterExampleState();
}
class _ShortcutFormatterExampleState extends State<ShortcutFormatterExample> {
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: CometChatConversationsWithMessages(
messageConfiguration: MessageConfiguration(
messageComposerConfiguration: MessageComposerConfiguration(
textFormatters: [ShortcutFormatter()],
),
)
),
),
);
}
}
Example
Here is the complete example for reference:
- Dart
import 'package:cometchat_chat_uikit/cometchat_chat_uikit.dart';
import 'package:flutter/material.dart';
class ShortcutFormatter extends CometChatTextFormatter {
void init() {
trackingCharacter ??= "!";
}
bool isShortcutTracking = false;
void prepareShortcuts(TextEditingController textEditingController) {
CometChat.callExtension('message-shortcuts', 'GET', '/v1/fetch', null,
onSuccess: (map) {
if (map.isNotEmpty) {
Map<String, dynamic> data = map["data"];
if (data.isNotEmpty) {
Map<String, dynamic> shortcuts = data["shortcuts"];
if (shortcuts.isNotEmpty) {
parseData(
shortcuts: shortcuts,
textEditingController: textEditingController);
}
}
}
}, onError: (error) {});
}
void parseData({Map<String, dynamic>? shortcuts, required TextEditingController textEditingController}) async {
if (shortcuts == null || shortcuts.isEmpty) {
suggestionListEventSink?.add([]);
if (onSearch != null) {
onSearch!(null);
}
CometChatUIEvents.hidePanel(composerId, CustomUIPosition.composerPreview);
} else {
CometChatUIEvents.hidePanel(composerId, CustomUIPosition.composerPreview);
if (suggestionListEventSink != null && shortcuts.isNotEmpty) {
List<SuggestionListItem> list = [];
shortcuts.forEach((key, value) => list.add(SuggestionListItem(
id: key,
title: "$key → $value",
onTap: () {
int cursorPos = textEditingController.selection.base.offset;
String textOnLeftOfValue = textEditingController.text.substring(0, cursorPos - 1);
String textOnRightOfValue = textEditingController.text.substring(cursorPos);
textEditingController.text = "$textOnLeftOfValue$value $textOnRightOfValue";
updatePreviousText(textEditingController.text);
textEditingController.selection = TextSelection(
baseOffset: cursorPos - 1 + "$value".length + 1,
extentOffset: cursorPos - 1 + "$value".length + 1,
);
resetMatchTracker();
isShortcutTracking = false;
CometChatUIEvents.hidePanel(
composerId, CustomUIPosition.composerPreview
);
})
));
suggestionListEventSink?.add(list);
}
}
}
void updatePreviousText(String text) {
if (previousTextEventSink != null) {
previousTextEventSink!.add(text);
}
}
void resetMatchTracker() {
suggestionListEventSink?.add([]);
if (onSearch != null) {
onSearch!(null);
}
}
TextStyle getMessageInputTextStyle(CometChatTheme theme) {
// TODO: implement getMessageInputTextStyle
throw UnimplementedError();
}
void handlePreMessageSend(BuildContext context, BaseMessage baseMessage) {
// TODO: implement handlePreMessageSend
}
void onChange(TextEditingController textEditingController, String previousText) {
var cursorPosition = textEditingController.selection.base.offset;
if (textEditingController.text.isEmpty) {
resetMatchTracker();
if (previousText.length > textEditingController.text.length) {
if (previousText[cursorPosition] == trackingCharacter && isShortcutTracking) {
isShortcutTracking = false;
if (onSearch != null) {
onSearch!(null);
}
CometChatUIEvents.hidePanel(composerId, CustomUIPosition.composerPreview);
}
return;
}
}
if (previousText.length > textEditingController.text.length) {
if (previousText[cursorPosition] == trackingCharacter) {
isShortcutTracking = false;
if (onSearch != null) {
onSearch!(null);
}
CometChatUIEvents.hidePanel(composerId, CustomUIPosition.composerPreview);
}
} else {
String previousCharacter = cursorPosition == 0 ? "" : textEditingController.text[cursorPosition - 1];
bool isSpace = cursorPosition == 1 || (textEditingController.text.length > 1 && cursorPosition > 1 && (textEditingController.text[cursorPosition - 2] == " " || textEditingController.text[cursorPosition - 2] == "\n"));
if (isShortcutTracking) {
isShortcutTracking = false;
if (onSearch != null) {
onSearch!(null);
}
CometChatUIEvents.hidePanel(composerId, CustomUIPosition.composerPreview);
} else if (previousCharacter == trackingCharacter && isSpace) {
isShortcutTracking = true;
if (onSearch != null) {
onSearch!(trackingCharacter);
}
CometChatUIEvents.showPanel(composerId, CustomUIPosition.composerPreview, (context) => getLoadingIndicator(context, cometChatTheme));
prepareShortcuts(textEditingController);
}
}
}
void onScrollToBottom(TextEditingController textEditingController) {
// TODO: implement onScrollToBottom
}
}
- Dart
import 'package:cometchat_chat_uikit/cometchat_chat_uikit.dart';
import 'package:flutter/material.dart';
import 'shortcut_formatter.dart';
class ShortcutFormatterExample extends StatefulWidget {
const ShortcutFormatterExample({super.key});
State<ShortcutFormatterExample> createState() => _ShortcutFormatterExampleState();
}
class _ShortcutFormatterExampleState extends State<ShortcutFormatterExample> {
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: CometChatConversationsWithMessages(
messageConfiguration: MessageConfiguration(
messageComposerConfiguration: MessageComposerConfiguration(
textFormatters: [ShortcutFormatter()],
),
)
),
),
);
}
}
- Android
- iOS