chat_uikit for Flutter
This guide gives a comprehensive overview into chat_uikit. The new chat_uikit is intended to provide developers with an efficient, plug-and-play, and highly customizable UI component library, helping you build complete and elegant IM applications that can easily satisfy most instant messaging scenarios. Please download the demo to try it out.
chat_uikit Guide
Introduction
This guide provides an overview and usage examples of the chat_uikit framework in Flutter development, and presents various components and functions of this UIKit, giving developers a good understanding of how chat_uikit works and how to use it efficiently.
Table of contents
- chat_uikit for Flutter
- chat_uikit Guide
- Development Environment
- Installation
- Structure
- Quick Start
- Advanced usage
- Design guide
Development Environment
environment:
sdk: '>=3.0.0 <4.0.0'
flutter: ">=3.19.0"
- ios 12+
- android minSDKVersion 24
Installation
flutter pub add em_chat_uikit
Structure
.
├── chat_uikit.dart // library
├── chat_uikit_alphabet_sort_helper.dart // Tool to correct the alphabetical order of contacts
├── chat_uikit_defines.dart // UIKit handler definition class
├── chat_uikit_emoji_data.dart // Message emoji data class
├── chat_uikit_localizations.dart // Internationalization tool class
├── chat_uikit_service // Secondary wrapping for the chat SDK wrapper class, used to adapt incompliant functions in the wrapping class to UIKit functions.
├── chat_uikit_settings.dart // Class to set functions, used to turn on or off or configure certain functions
├── chat_uikit_time_formatter.dart // Tool to set the displayed time format
├── provider // User attribute tool
│ ├── chat_uikit_profile.dart // User attribute object, including the user's avatar, nickname, and remarks.
│ └── chat_uikit_provider.dart // User attribute provider, used to provide user attribute data in the UIKit.
├── sdk_service // Wrapping for the chat SDK, used to wrap APIs in the chat SDK that are available to developers. The UIKit interacts with the wrapping class, instead of calling APIs in the chat SDK.
├── tools // Internal tool class
│ ├── chat_uikit_context.dart // Data context to store certain states.
│ ├── chat_uikit_conversation_extension.dart // Processed class of the conversation list to pre-process certain properties.
│ ├── chat_uikit_file_size_tool.dart // Tool to calculate the displayed file size.
│ ├── chat_uikit_helper.dart // Internal class to calculate the border radius
│ ├── chat_uikit_highlight_tool.dart // Tool class to calculate the component highlight value
│ ├── chat_uikit_image_loader.dart // Image loading tool class
│ ├── chat_uikit_message_extension.dart // Message processing class that pre-processes certain properties
│ ├── chat_uikit_time_tool.dart // Default time format class
│ ├── chat_uikit_url_helper.dart // Tool class for URL preview
│ └── safe_disposed.dart // Internal processing class for ChangeNotifier
├── ui // UI components
│ ├── components // Components
│ ├── controllers // View/widget controllers
│ ├── custom // UI customization
│ ├── models // models
│ ├── route // Route component in the UIKit
│ ├── views // Views
│ └── widgets // Widgets
└── universal // Internal class
Quick Start
- Create a project.
flutter create uikit_quick_start --platforms=android,ios
- Add dependencies.
cd uikit_quick_start
flutter pub add em_chat_uikit
flutter pub get
- Add permissions.
- iOS: Add permissions in
<project root>/ios/Runner/Info.plist
.
NSPhotoLibraryUsageDescription
NSCameraUsageDescription
NSMicrophoneUsageDescription
- Initialize the UIKit.
import 'package:em_chat_uikit/chat_uikit.dart';
...
void main() {
ChatUIKit.instance
.init(options: Options(appKey: <!--Your AppKey-->, autoLogin: false))
.then((value) {
runApp(const MyApp());
});
}
- Log in to the UIKit.
Future<void> login() async {
try {
await ChatUIKit.instance.loginWithToken(
userId: '<!--user id-->',
token: '<!--user token-->');
} catch (e) {
debugPrint('login error: $e');
}
}
- Create the chat page.
class ChatPage extends StatefulWidget {
const ChatPage({required this.chatterId, super.key});
final String chatterId;
@override
State<ChatPage> createState() => _ChatPageState();
}
class _ChatPageState extends State<ChatPage> {
@override
Widget build(BuildContext context) {
return MessagesView(
profile: ChatUIKitProfile.contact(
id: widget.chatterId,
),
);
}
}
Complete code:
import 'package:flutter/material.dart';
import 'package:em_chat_uikit/chat_uikit.dart';
const String appKey = '';
const String userId = '';
const String token = '';
const String chatterId = '';
void main() {
ChatUIKit.instance
.init(options: Options(appKey: appKey, autoLogin: false))
.then((value) {
runApp(MyApp());
});
}
class MyApp extends StatelessWidget {
MyApp({super.key});
final ChatUIKitLocalizations _localization = ChatUIKitLocalizations();
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
supportedLocales: _localization.supportedLocales,
localizationsDelegates: _localization.localizationsDelegates,
localeResolutionCallback: _localization.localeResolutionCallback,
locale: _localization.currentLocale,
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: login,
child: const Text('Login'),
),
ElevatedButton(
onPressed: chat,
child: const Text('Chat'),
),
],
),
),
);
}
Future<void> login() async {
try {
await ChatUIKit.instance.loginWithToken(
userId: userId,
token: token,
);
} catch (e) {
debugPrint('login error: $e');
}
}
void chat() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const ChatPage(
chatterId: chatterId,
),
),
);
}
}
class ChatPage extends StatefulWidget {
const ChatPage({required this.chatterId, super.key});
final String chatterId;
@override
State<ChatPage> createState() => _ChatPageState();
}
class _ChatPageState extends State<ChatPage> {
@override
Widget build(BuildContext context) {
return MessagesView(
profile: ChatUIKitProfile.contact(
id: widget.chatterId,
),
);
}
}
Advanced usage
Provider
Provider is a data provider. If user data or group data needs to be displayed, the UIKit will request data via the Provider and you need to return data to the Provider. The UIKit, once getting your data, will refresh the UI to show your data. Following is an example of Provider (example/lib/tool/user_provider_widget.dart
).
Example
class UserProviderWidget extends StatefulWidget {
const UserProviderWidget({required this.child, super.key});
final Widget child;
@override
State<UserProviderWidget> createState() => _UserProviderWidgetState();
}
class _UserProviderWidgetState extends State<UserProviderWidget>
with GroupObserver {
@override
void initState() {
super.initState();
ChatUIKit.instance.addObserver(this);
// Open DB
UserDataStore().init(onOpened: onOpened);
// Set Provider Handler
ChatUIKitProvider.instance.profilesHandler = onProfilesRequest;
}
@override
void dispose() {
ChatUIKit.instance.removeObserver(this);
super.dispose();
}
@override
Widget build(BuildContext context) {
return widget.child;
}
void onOpened() async {
// 1. Fill all stored data into uikit.
await addAllUserInfoToProvider();
// 2. Load group information, and check if it has been filled into uikit. If not, fetch data from the server and then fill it into uikit.
await loadGroupInfos();
// 2. Load user information, and check if it has been filled into uikit. If not, fetch data from the server and then fill it into uikit.
await loadUserInfos();
// 3. Fetch current user information, then fill it into uikit.
await fetchCurrentUserInfo();
}
Future<void> fetchCurrentUserInfo() async {
try {
// Do not retrieve your own user data from the db, always fetch the latest data from the server.
Map<String, UserInfo> map = await ChatUIKit.instance
.fetchUserInfoByIds([ChatUIKit.instance.currentUserId!]);
ChatUIKitProfile profile = ChatUIKitProfile.contact(
id: map.values.first.userId,
nickname: map.values.first.nickName,
avatarUrl: map.values.first.avatarUrl,
);
UserDataStore().saveUserData(profile);
ChatUIKitProvider.instance.addProfiles([profile]);
} catch (e) {
debugPrint('fetchCurrentUserInfo error: $e');
}
}
// This method is called when uikit needs to display user information and the cache does not exist;
// it requires fetching and storing the information in the db based on user attributes.
List<ChatUIKitProfile>? onProfilesRequest(List<ChatUIKitProfile> profiles) {
List<String> userIds = profiles
.where((e) => e.type == ChatUIKitProfileType.contact)
.map((e) => e.id)
.toList();
if (userIds.isNotEmpty) {
fetchUserInfos(userIds);
}
List<String> groupIds = profiles
.where((e) => e.type == ChatUIKitProfileType.group)
.map((e) => e.id)
.toList();
updateGroupsProfile(groupIds);
return profiles;
}
// When a group is created by yourself, it is necessary to fill the group information into uikit.
@override
void onGroupCreatedByMyself(Group group) async {
ChatUIKitProfile profile =
ChatUIKitProfile.group(id: group.groupId, groupName: group.name);
ChatUIKitProvider.instance.addProfiles([profile]);
// save to db
UserDataStore().saveUserData(profile);
}
// When the group name is changed by yourself, it is necessary to update the group information in uikit.
@override
void onGroupNameChangedByMeSelf(Group group) {
ChatUIKitProfile? profile =
ChatUIKitProvider.instance.getProfileById(group.groupId);
profile = profile?.copyWith(name: group.name) ??
ChatUIKitProfile.group(
id: group.groupId,
groupName: group.name,
);
ChatUIKitProvider.instance.addProfiles([profile]);
// save to db
UserDataStore().saveUserData(profile);
}
// Fill all stored data into uikit.
Future<void> addAllUserInfoToProvider() async {
List<ChatUIKitProfile> list = await UserDataStore().loadAllProfiles();
ChatUIKitProvider.instance.addProfiles(list);
}
// Load group information, and check if it has been filled into uikit. If not, fetch data from the server and then fill it into uikit.
Future<void> loadGroupInfos() async {
List<Group> groups = await ChatUIKit.instance.getJoinedGroups();
List<ChatUIKitProfile> profiles = groups
.map((e) => ChatUIKitProfile.group(id: e.groupId, groupName: e.name))
.toList();
if (profiles.isNotEmpty) {
UserDataStore().saveUserDatas(profiles);
ChatUIKitProvider.instance.addProfiles(profiles);
}
}
Future<void> updateGroupsProfile(List<String> groupIds) async {
List<ChatUIKitProfile> list = [];
for (var groupId in groupIds) {
try {
Group group = await ChatUIKit.instance.fetchGroupInfo(groupId: groupId);
ChatUIKitProfile profile = ChatUIKitProfile.group(
id: group.groupId,
groupName: group.name,
avatarUrl: group.extension,
);
list.add(profile);
} on ChatError catch (e) {
if (e.code == 600) {
// 600 indicates the group does not exist, unable to fetch data, providing default data.
ChatUIKitProfile profile = ChatUIKitProfile.group(id: groupId);
list.add(profile);
}
debugPrint('loadGroupInfo error: $e');
}
}
UserDataStore().saveUserDatas(list);
ChatUIKitProvider.instance.addProfiles(list);
}
// Load user information, and check if it has been filled into uikit. If not, fetch data from the server and then fill it into uikit.
Future<void> loadUserInfos() async {
try {
Map<String, ChatUIKitProfile> map =
ChatUIKitProvider.instance.profilesCache;
List<Contact> contacts = await ChatUIKit.instance.getAllContacts();
contacts.removeWhere((element) => map.keys.contains(element.userId));
if (contacts.isNotEmpty) {
List<String> userIds = contacts.map((e) => e.userId).toList();
fetchUserInfos(userIds);
}
} catch (e) {
debugPrint('loadUserInfos error: $e');
}
}
void fetchUserInfos(List<String> userIds) async {
try {
Map<String, UserInfo> map =
await ChatUIKit.instance.fetchUserInfoByIds(userIds);
List<ChatUIKitProfile> list = map.values
.map((e) => ChatUIKitProfile.contact(
id: e.userId, nickname: e.nickName, avatarUrl: e.avatarUrl))
.toList();
if (list.isNotEmpty) {
UserDataStore().saveUserDatas(list);
ChatUIKitProvider.instance.addProfiles(list);
}
} catch (e) {
debugPrint('fetchUserInfos error: $e');
}
}
}
Usage
A hanlder profilesHandler
is required before the use of ChatUIKitProvider
. After that, when related information needs to be displayed, UIKit will return you a default ChatUIKitProfile
object via the handler and you need to return a ChatUIKitProfile
object. In this case, you are advised to return a ChatUIKitProfile
object for the placeholding purpose. When you have obtained the correct ChatUIKitProfile
object from your server or database, pass it to the UIKit using the ChatUIKitProvider.instance.addProfiles(list)
method. Receiving the object, the UIKit refreshes the UI and caches it for subsequent displaying. Take the following steps to use ChatUIKitProvider:
- Set
profilesHandler
upon the app start and login.
ChatUIKitProvider.instance.profilesHandler = onProfilesRequest;
- Set the user data and group data in the database to the UIKit via
ChatUIKitProvider
.
List<ChatUIKitProfile> list = await UserDataStore().loadAllProfiles();
ChatUIKitProvider.instance.addProfiles(list);
- When
profilesHandler
is executed, you can first returnChatUIKitProfile
for the placeholding purpose, get data from the server, and then save the data to the database and pass it to the UIKit.
List<ChatUIKitProfile>? onProfilesRequest(List<ChatUIKitProfile> profiles) {
List<String> userIds = profiles
.where((e) => e.type == ChatUIKitProfileType.contact)
.map((e) => e.id)
.toList();
if (userIds.isNotEmpty) {
fetchUserInfos(userIds);
}
List<String> groupIds = profiles
.where((e) => e.type == ChatUIKitProfileType.group)
.map((e) => e.id)
.toList();
updateGroupsProfile(groupIds);
return profiles;
}
Configuration items
ChatUIKit allows easy style customization via ChatUIKitSettings.
import 'chat_uikit.dart';
import 'package:flutter/material.dart';
enum CornerRadius { extraSmall, small, medium, large }
class ChatUIKitSettings {
/// Specifies the corner radius for the avatars in the uikit.
static CornerRadius avatarRadius = CornerRadius.medium;
/// Specifies the corner radius of the search box.
static CornerRadius searchBarRadius = CornerRadius.small;
/// Specifies the corner radius of the input box.
static CornerRadius inputBarRadius = CornerRadius.medium;
/// Default avatar placeholder image.
static ImageProvider? avatarPlaceholder;
/// The corner radius for the dialog.
static ChatUIKitDialogRectangleType dialogRectangleType =
ChatUIKitDialogRectangleType.filletCorner;
/// Default display style of message bubbles.
static ChatUIKitMessageListViewBubbleStyle messageBubbleStyle =
ChatUIKitMessageListViewBubbleStyle.arrow;
/// Whether to show avatars in the conversation list.
static bool showConversationListAvatar = true;
/// Whether to show unread message count in the conversation list.
static bool showConversationListUnreadCount = true;
// Mute icon displayed in the conversation list.
static ImageProvider? conversationListMuteImage;
/// Message long press menu.
static List<ChatUIKitActionType> msgItemLongPressActions = [
ChatUIKitActionType.reaction,
ChatUIKitActionType.copy, // only text message.
ChatUIKitActionType.forward,
ChatUIKitActionType.thread, // only group message.
ChatUIKitActionType.reply,
ChatUIKitActionType.recall,
ChatUIKitActionType.edit, // only text message.
ChatUIKitActionType.multiSelect,
ChatUIKitActionType.pinMessage,
ChatUIKitActionType.translate, // only text message.
ChatUIKitActionType.report,
ChatUIKitActionType.delete,
];
/// Whether to enable the functionality of input status for one-on-one chat messages.
static bool enableTypingIndicator = true;
/// Whether to enable the thread feature.
static bool enableMessageThread = true;
/// Whether to enable message translation feature.
static bool enableMessageTranslation = true;
/// Message translation target language.
static String translateTargetLanguage = 'zh-Hans';
/// Whether to enable message reaction feature.
static bool enableMessageReaction = true;
/// Message reaction emojis in the bottom sheet title. These emojis need to be included in the emoji list [ChatUIKitEmojiData.emojiList].
static List<String> favoriteReaction = [
'\u{1F44D}',
'\u{2764}',
'\u{1F609}',
'\u{1F928}',
'\u{1F62D}',
'\u{1F389}',
];
/// Regular expression for the default message URL
static RegExp defaultUrlRegExp = RegExp(
r'(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+',
caseSensitive: false,
);
/// Whether to enable the message pinning feature
static bool enablePinMsg = true;
/// Whether to enable message quoting feature
static bool enableMessageReply = true;
/// Whether to enable message recall feature
static bool enableMessageRecall = true;
/// Time limit for message recall, in seconds
static int recallExpandTime = 120;
/// Whether to enable message editing feature
static bool enableMessageEdit = true;
/// Whether to enable message reporting feature
static bool enableMessageReport = true;
/// Message report tags, can be customized. The reasons for reporting should be written in the localization file, and the key for the reason in the localization file should be consistent with the tag. For example, [ChatUIKitLocal.reportTarget1]
static List<String> reportMessageTags = [
'tag1',
'tag2',
'tag3',
'tag4',
'tag5',
'tag6',
'tag7',
'tag8',
'tag9',
];
/// Whether to enable message multi-selection feature
static bool enableMessageMultiSelect = true;
/// Whether to enable message forwarding feature
static bool enableMessageForward = true;
/// Alphabetical order of `showName` of contacts. If there are Chinese characters, you can redefine the initials using [ChatUIKitAlphabetSortHelper].
static String sortAlphabetical = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ#';
}
Internationalization
UIKit provides the internationalization function. You need to set internationalization information in MaterialApp
when integrating the UIKit.
final ChatUIKitLocalizations _localization = ChatUIKitLocalizations();
...
@override
Widget build(BuildContext context) {
return MaterialApp(
supportedLocales: _localization.supportedLocales,
localizationsDelegates: _localization.localizationsDelegates,
localeResolutionCallback: _localization.localeResolutionCallback,
locale: _localization.currentLocale,
...
);
}
To add the supported languages, you can first use ChatUIKitLocalizations.addLocales
, and then call ChatUIKitLocalizations.resetLocales
.
Following is an example of adding French.
_localization.addLocales(locales: const [
ChatLocal('fr', {
ChatUIKitLocal.conversationsViewSearchHint: 'Recherche',
})
]);
_localization.resetLocales();
Theme
UIKit comes with two themes: light and dark, with the former as the default. When setting the theme, you are advised to add ChatUIKitTheme
component as root node of the UIKit.
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
builder: (context, child) {
/// add theme support
return ChatUIKitTheme(
font: ChatUIKitFont(),
color: ChatUIKitColor.light(), // ChatUIKitColor.dark()
child: child!,
);
},
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
ChatUIKitColor
can be customized by adjusting hue
. For example, adjust the hue
value in light mode.
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
builder: (context, child) {
return ChatUIKitTheme(
color: ChatUIKitColor.light(
primaryHue: 203,
secondaryHue: 155,
errorHue: 350,
neutralHue: 203,
neutralSpecialHue: 220,
),
child: child!,
);
},
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
ChatUIKitFont
allows you to set the font size. For example, you can pass different types of ChatUIKitFontSize
to ChatUIKitFont.fontSize(fontSize: ChatUIKitFontSize.normal)
to change the font size of the UIKit.
Route interception and customization
The UIKit uses pushNamed
to implement redirection, with the ChatUIKitViewArguments
object of the target redirection page passed. You can intercept onGenerateRoute(RouteSettings settings)
and parse settings.name
to get the target page for redirection. Then, you can reset the ChatUIKitViewArguments
parameter for redirection interception and page customization. The name of the target redirection page is specified in chat_uikit_route_names.dart
.
For details on route interception, you can refer to example/lib/custom/chat_route_filter.dart
.
Event interception and error handling
When the UIKit starts to call the chat SDK, ChatSDKEventsObserver.onChatSDKEventBegin
is triggered. When the call ends, ChatSDKEventsObserver.onChatSDKEventEnd
is triggered. If an error occurs, ChatError
is reported.
class SDKEventHandlerPage extends StatefulWidget {
const SDKEventHandlerPage({super.key});
@override
State<SDKEventHandlerPage> createState() => _SDKEventHandlerPageState();
}
class _SDKEventHandlerPageState extends State<SDKEventHandlerPage>
with ChatSDKEventsObserver {
@override
void initState() {
ChatUIKit.instance.addObserver(this);
super.initState();
}
@override
void dispose() {
ChatUIKit.instance.removeObserver(this);
super.dispose();
}
/// When the call to an SDK method starts, you can display different prompt windows based on different events.
@override
void onChatSDKEventBegin(ChatSDKEvent event) {}
/// When the call to an SDK method call ends, you can end the prompt window display at this time. If there is an error, you can display the corresponding prompt message.
@override
void onChatSDKEventEnd(ChatSDKEvent event, ChatError? error) {}
...
}
For more information, you can refer to example/lib/tool/toast_page.dart
.
For other events than those of the chat SDK, ChatUIKitEventsObservers.onChatUIKitEventsReceived
is triggered.
class UIKitEventHandlePage extends StatefulWidget {
const UIKitEventHandlePage({super.key});
@override
State<UIKitEventHandlePage> createState() => _UIKitEventHandlePageState();
}
class _UIKitEventHandlePageState extends State<UIKitEventHandlePage>
with ChatUIKitEventsObservers {
@override
void initState() {
ChatUIKit.instance.addObserver(this);
super.initState();
}
@override
void dispose() {
ChatUIKit.instance.removeObserver(this);
super.dispose();
}
/// This method is used to pass events from ChatUIKit to developers.
@override
void onChatUIKitEventsReceived(ChatUIKitEvent event) {}
...
}
For more information, see example/lib/tool/toast_page.dart
.
Connection status change and login token expiration callback
When the connection status or login status changes, the corresponding event in ChatUIKit.instance.connectHandler
is triggered.
ChatUIKit.instance.connectHandler(
onUserAuthenticationFailed: () {},
onUserDidChangePassword: () {},
onUserDidForbidByServer: () {},
onUserDidLoginFromOtherDevice: (info) {},
onUserDidLoginTooManyDevice: () {},
onUserDidRemoveFromServer: () {},
onUserKickedByOtherDevice: () {},
onConnected: () {},
onDisconnected: () {},
onTokenWillExpire: () {},
onTokenDidExpire: () {},
onAppActiveNumberReachLimit: () {},
);
For more information, you can refer to example/lib/tool/token_status_handler_widget.dart
.
Message time formatting
UIKit presents time in the default format. You can call ChatUIKitTimeFormatter
to alter the way the time is formatted.
ChatUIKitTimeFormatter.instance.formatterHandler = (context, type, time) {
return 'formatter time'; // return formatted time, e.g. 12:00 PM
};
Correction of the alphabetical order of contacts
For example, if a contact name contains other characters than English letters, you can use ChatUIKitAlphabetSortHelper
to sort contacts in the alphabetical order.
ChatUIKitAlphabetSortHelper.instance.sortHandler = (String? groupId, String userId, String showName) {
// Return the first letter of the showName for sorting, especially useful for sorting Chinese characters
return PinyinHelper.getFirstWordPinyin(showName);
};
Design guide
For any questions about design guidelines and details, you can add comments to the Figma design draft and mention our designer Stevie Jiang.
Libraries
- chat_uikit
- chat_uikit_alphabet_sort_helper
- chat_uikit_emoji_data
- chat_uikit_localizations
- chat_uikit_service/actions/chat_uikit_chat_actions
- chat_uikit_service/actions/chat_uikit_contact_actions
- chat_uikit_service/actions/chat_uikit_events_actions
- chat_uikit_service/actions/chat_uikit_group_actions
- chat_uikit_service/actions/chat_uikit_notification_actions
- chat_uikit_service/chat_uikit_action_events
- chat_uikit_service/chat_uikit_service
- chat_uikit_service/observers/chat_uikit_chat_observers
- chat_uikit_service/observers/chat_uikit_contact_observers
- chat_uikit_service/observers/chat_uikit_events_observers
- chat_uikit_service/observers/chat_uikit_group_observers
- chat_uikit_service/observers/chat_uikit_multi_observers
- chat_uikit_service/observers/chat_uikit_thread_observers
- chat_uikit_settings
- chat_uikit_time_formatter
- provider/chat_uikit_profile
- provider/chat_uikit_provider
- sdk_service/actions/chat_actions
- sdk_service/actions/contact_actions
- sdk_service/actions/group_actions
- sdk_service/actions/notification_actions
- sdk_service/actions/presence_actions
- sdk_service/actions/room_actions
- sdk_service/actions/thread_actions
- sdk_service/actions/user_info_actions
- sdk_service/chat_sdk_define
- sdk_service/chat_sdk_service
- sdk_service/chat_sdk_service_defines
- sdk_service/chat_uikit_insert_tools
- sdk_service/observers/action_event_observer
- sdk_service/observers/chat_observer
- sdk_service/observers/connect_observer
- sdk_service/observers/contact_observer
- sdk_service/observers/group_observer
- sdk_service/observers/message_observer
- sdk_service/observers/multi_observer
- sdk_service/observers/presence_observer
- sdk_service/observers/room_observer
- sdk_service/observers/thread_observer
- sdk_service/wrappers/chat_wrapper
- sdk_service/wrappers/connect_wrapper
- sdk_service/wrappers/contact_wrapper
- sdk_service/wrappers/group_wrapper
- sdk_service/wrappers/message_wrapper
- sdk_service/wrappers/multi_wrapper
- sdk_service/wrappers/notification_wrapper
- sdk_service/wrappers/presence_wrapper
- sdk_service/wrappers/room_wrapper
- sdk_service/wrappers/thread_wrapper
- sdk_service/wrappers/user_info_wrapper
- tools/chat_string_extension
- tools/chat_uikit_context
- tools/chat_uikit_conversation_extension
- tools/chat_uikit_file_size_tool
- tools/chat_uikit_helper
- tools/chat_uikit_highlight_tool
- tools/chat_uikit_image_loader
- tools/chat_uikit_message_extension
- tools/chat_uikit_time_tool
- tools/chat_uikit_url_helper
- tools/safe_disposed
- ui/components/block_list_view
- ui/components/contact_list_view
- ui/components/conversation_list_view
- ui/components/group_list_view
- ui/components/group_member_list_view
- ui/components/message_list_view
- ui/components/new_requests_list_view
- ui/components/pin_message_list_view
- ui/components/thread_message_list_view
- ui/controllers/block_list_view_controller
- ui/controllers/chat_uikit_list_view_controller_base
- ui/controllers/contact_list_view_controller
- ui/controllers/conversation_list_view_controller
- ui/controllers/group_list_view_controller
- ui/controllers/group_member_list_view_controller
- ui/controllers/messages_view_controller
- ui/controllers/new_request_list_view_controller
- ui/controllers/pin_message_list_view_controller
- ui/controllers/thread_members_view_controller
- ui/controllers/thread_messages_view_controller
- ui/custom/custom_tab_indicator
- ui/models/alphabetical_item_model
- ui/models/chat_uikit_list_item_model_base
- ui/models/contact_item_model
- ui/models/conversation_item_model
- ui/models/group_item_model
- ui/models/message_model
- ui/models/new_request_item_model
- ui/models/pin_list_item_model
- ui/models/quote_mode
- ui/route/chat_uikit_route
- ui/route/chat_uikit_route_names
- ui/route/chat_uikit_view_observer
- ui/route/view_arguments/change_info_view_arguments
- ui/route/view_arguments/contact_details_view_arguments
- ui/route/view_arguments/contacts_view_arguments
- ui/route/view_arguments/conversations_view_arguments
- ui/route/view_arguments/create_group_view_arguments
- ui/route/view_arguments/current_user_info_view_arguments
- ui/route/view_arguments/forward_message_select_view_arguments
- ui/route/view_arguments/forward_messages_view_arguments
- ui/route/view_arguments/group_add_members_view_arguments
- ui/route/view_arguments/group_change_owner_view_arguments
- ui/route/view_arguments/group_delete_members_view_arguments
- ui/route/view_arguments/group_details_view_arguments
- ui/route/view_arguments/group_members_view_arguments
- ui/route/view_arguments/group_mention_view_arguments
- ui/route/view_arguments/groups_view_arguments
- ui/route/view_arguments/messages_view_arguments
- ui/route/view_arguments/new_request_details_view_arguments
- ui/route/view_arguments/new_requests_view_arguments
- ui/route/view_arguments/report_message_view_arguments
- ui/route/view_arguments/search_group_members_view_arguments
- ui/route/view_arguments/search_history_view_arguments
- ui/route/view_arguments/search_view_arguments
- ui/route/view_arguments/select_contact_view_arguments
- ui/route/view_arguments/show_image_view_arguments
- ui/route/view_arguments/show_video_view_arguments
- ui/route/view_arguments/thread_members_view_arguments
- ui/route/view_arguments/thread_messages_view_arguments
- ui/route/view_arguments/threads_view_arguments
- ui/route/view_arguments/view_arguments_base
- ui/views/change_info_view
- ui/views/contact_details_view
- ui/views/contacts_view
- ui/views/conversations_view
- ui/views/create_group_view
- ui/views/current_user_info_view
- ui/views/forward_message_select_view
- ui/views/forward_messages_view
- ui/views/group_add_members_view
- ui/views/group_change_owner_view
- ui/views/group_delete_members_view
- ui/views/group_details_view
- ui/views/group_members_view
- ui/views/group_mention_view
- ui/views/groups_view
- ui/views/messages_view
- ui/views/new_request_details_view
- ui/views/new_requests_view
- ui/views/report_message_view
- ui/views/search_group_members_view
- ui/views/search_history_view
- ui/views/search_view
- ui/views/select_contact_view
- ui/views/show_image_view
- ui/views/show_video_view
- ui/views/thread_members_view
- ui/views/thread_messages_view
- ui/views/threads_view
- ui/widgets/chat_uikit_alphabetical_widget
- ui/widgets/chat_uikit_app_bar
- ui/widgets/chat_uikit_avatar
- ui/widgets/chat_uikit_badge
- ui/widgets/chat_uikit_bottom_sheet
- ui/widgets/chat_uikit_dialog
- ui/widgets/chat_uikit_downloads_helper_widget
- ui/widgets/chat_uikit_emoji_panel
- ui/widgets/chat_uikit_emoji_rich_text_widget
- ui/widgets/chat_uikit_list_view
- ui/widgets/chat_uikit_message_reaction_info
- ui/widgets/chat_uikit_message_status_widget
- ui/widgets/chat_uikit_quote_widget
- ui/widgets/chat_uikit_reg_exp_text
- ui/widgets/chat_uikit_reply_bar
- ui/widgets/chat_uikit_search_widget
- ui/widgets/chat_uikit_show_image_widget
- ui/widgets/chat_uikit_show_video_widget
- ui/widgets/edit_bar/chat_uikit_edit_bar
- ui/widgets/input_bar/chat_uikit_input_bar
- ui/widgets/input_bar/chat_uikit_selection_controls
- ui/widgets/input_bar/custom_text_editing_controller
- ui/widgets/list_view_items/chat_uikit_alphabetical_list_view_item
- ui/widgets/list_view_items/chat_uikit_contact_list_view_item
- ui/widgets/list_view_items/chat_uikit_conversation_list_view_item
- ui/widgets/list_view_items/chat_uikit_details_list_view_item
- ui/widgets/list_view_items/chat_uikit_group_list_view_item
- ui/widgets/list_view_items/chat_uikit_list_view_more_item
- ui/widgets/list_view_items/chat_uikit_new_request_list_view_item
- ui/widgets/list_view_items/chat_uikit_reaction_widget
- ui/widgets/list_view_items/chat_uikit_search_list_view_item
- ui/widgets/list_view_items/message_list_view_items/chat_uikit_message_bubble_widget
- ui/widgets/list_view_items/message_list_view_items/chat_uikit_message_list_view_alert_item
- ui/widgets/list_view_items/message_list_view_items/chat_uikit_message_list_view_message_item
- ui/widgets/list_view_items/message_list_view_items/chat_uikit_message_reactions_row
- ui/widgets/list_view_items/message_list_view_items/chat_uikit_message_thread_widget
- ui/widgets/list_view_items/message_list_view_items/message_widget/chat_uikit_card_bubble_widget
- ui/widgets/list_view_items/message_list_view_items/message_widget/chat_uikit_combine_bubble_widget
- ui/widgets/list_view_items/message_list_view_items/message_widget/chat_uikit_file_bubble_widget
- ui/widgets/list_view_items/message_list_view_items/message_widget/chat_uikit_image_bubble_widget
- ui/widgets/list_view_items/message_list_view_items/message_widget/chat_uikit_nonsupport_bubble_widget
- ui/widgets/list_view_items/message_list_view_items/message_widget/chat_uikit_text_bubble_widget
- ui/widgets/list_view_items/message_list_view_items/message_widget/chat_uikit_video_bubble_widget
- ui/widgets/list_view_items/message_list_view_items/message_widget/chat_uikit_voice_bubble_widget
- ui/widgets/popup_menu/chat_uikit_position_widget
- ui/widgets/record_bar/chat_uikit_record_bar
- ui/widgets/record_bar/src/error_code
- ui/widgets/record_bar/src/record_bar
- ui/widgets/record_bar/src/record_data
- ui/widgets/record_bar/src/record_error
- ui/widgets/record_bar/src/record_types
- ui/widgets/record_bar/src/water_ripple
- universal/chat_uikit_action_model
- universal/chat_uikit_defines
- universal/chat_uikit_log
- universal/chat_uikit_tools
- universal/defines
- universal/inner_headers