Skip to main content

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

  1. Create the ShortcutFormatter Class: Define the ShortcutFormatter class by extending the CometChatTextFormatter class.
import 'package:cometchat_chat_uikit/cometchat_chat_uikit.dart';

class ShortcutFormatter extends CometChatTextFormatter {

}
  1. Init: Initialize the messageShortcuts map and shortcuts list in the init().

void init() {
trackingCharacter ??= "!";
}
  1. Prepare Shortcuts: Implement the prepareShortcuts() method to fetch shortcuts from the server using CometChat extension.
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);
}
}
  1. Override onChange Method: Override the onChange() method to search for shortcuts based on the entered query.

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);
}
}
}
  1. Handle Scroll to Bottom: Override the onScrollToBottom() method if needed.

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.

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:

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
}
}
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()],
),
)
),
),
);
}
}
Image