diff --git a/README.md b/README.md
index 9f4a3c40..e530817c 100644
--- a/README.md
+++ b/README.md
@@ -9,8 +9,9 @@
ChatView is a Flutter package that allows you to integrate a highly customizable chat UI in your
Flutter applications with [Flexible Backend Integration][chatViewConnect].
-[//]: # (TODO-YASH: USE MAIN BRANCH TO DISPLAY PREVIEW GIF)
-
+| ChatViewList | ChatView |
+|--------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------|
+|  |  |
## Features
@@ -23,6 +24,7 @@ Flutter applications with [Flexible Backend Integration][chatViewConnect].
- User online status indicators
- Typing indicators for active users
- Unread message count badges
+- Header and Footer support for additional widgets
- Highly customizable UI components
- Plug-and-play backend support using [chatview_connect][chatViewConnect]
diff --git a/doc/documentation.md b/doc/documentation.md
index 166ec91d..a4659bc9 100644
--- a/doc/documentation.md
+++ b/doc/documentation.md
@@ -5,7 +5,9 @@ Flutter applications with [Flexible Backend Integration](https://pub.dev/package
## Preview
-
+| ChatViewList | ChatView |
+|--------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------|
+|  |  |
## Features
@@ -42,12 +44,12 @@ Flutter applications with [Flexible Backend Integration](https://pub.dev/package
For a live web demo, visit [Chat View Example](https://simformsolutionspvtltd.github.io/chatview/).
-## Compatibility with [`chatview_connect`](https://pub.dev/packages/chatview_connect)
+## Compatibility with [chatview_connect](https://pub.dev/packages/chatview_connect)
-| `chatview` version | [`chatview_connect`](https://pub.dev/packages/chatview_connect) version |
-|--------------------|-------------------------------------------------------------------------|
-| `>=2.4.1 <3.0.0` | `0.0.1` |
-| `>= 3.0.0` | `0.0.2` |
+| `chatview` version | [chatview_connect](https://pub.dev/packages/chatview_connect) version |
+|--------------------|-----------------------------------------------------------------------|
+| `>=2.4.1 <3.0.0` | `0.0.1` |
+| `>= 3.0.0` | `0.0.2` |
## Compatible Message Types
diff --git a/example/assets/vectors/ai_logo.svg b/example/assets/vectors/ai_logo.svg
new file mode 100644
index 00000000..d92d5fef
--- /dev/null
+++ b/example/assets/vectors/ai_logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/assets/vectors/camera_2.svg b/example/assets/vectors/camera_2.svg
new file mode 100644
index 00000000..963829a9
--- /dev/null
+++ b/example/assets/vectors/camera_2.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/assets/vectors/chat_discovery_ai.svg b/example/assets/vectors/chat_discovery_ai.svg
new file mode 100644
index 00000000..37554f44
--- /dev/null
+++ b/example/assets/vectors/chat_discovery_ai.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/assets/vectors/check_mark.svg b/example/assets/vectors/check_mark.svg
new file mode 100644
index 00000000..0a7d0394
--- /dev/null
+++ b/example/assets/vectors/check_mark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/assets/vectors/create_pen.svg b/example/assets/vectors/create_pen.svg
new file mode 100644
index 00000000..826d852b
--- /dev/null
+++ b/example/assets/vectors/create_pen.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/assets/vectors/phone.svg b/example/assets/vectors/phone.svg
new file mode 100644
index 00000000..6a047c6c
--- /dev/null
+++ b/example/assets/vectors/phone.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/assets/vectors/pinned.svg b/example/assets/vectors/pinned.svg
new file mode 100644
index 00000000..31306864
--- /dev/null
+++ b/example/assets/vectors/pinned.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/assets/vectors/sticker.svg b/example/assets/vectors/sticker.svg
new file mode 100644
index 00000000..3ece50e2
--- /dev/null
+++ b/example/assets/vectors/sticker.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/assets/vectors/video.svg b/example/assets/vectors/video.svg
new file mode 100644
index 00000000..e8f3ef66
--- /dev/null
+++ b/example/assets/vectors/video.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/lib/chat_operations.dart b/example/lib/chat_operations.dart
deleted file mode 100644
index f36dc588..00000000
--- a/example/lib/chat_operations.dart
+++ /dev/null
@@ -1,124 +0,0 @@
-import 'dart:math';
-
-import 'package:chatview/chatview.dart';
-import 'package:flutter/material.dart';
-
-class ChatOperations extends StatelessWidget {
- const ChatOperations({required this.controller, super.key});
-
- final ChatViewListController controller;
-
- @override
- Widget build(BuildContext context) {
- return Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- FloatingActionButton.small(
- child: const Icon(Icons.add),
- onPressed: () {
- controller.addChat(
- ChatViewListItem(
- id: DateTime.now().millisecondsSinceEpoch.toString(),
- name: 'New Chat ${DateTime.now().millisecondsSinceEpoch}',
- lastMessage: Message(
- message: 'Hello, this is a new chat!',
- createdAt: DateTime.now(),
- sentBy: 'User',
- id: 'msg-${DateTime.now().millisecondsSinceEpoch}',
- ),
- ),
- );
- },
- ),
- const SizedBox(width: 6),
- FloatingActionButton.small(
- child: const Icon(Icons.remove),
- onPressed: () {
- final key = controller.chatListMap.keys.firstOrNull;
- if (key == null) return;
- controller.removeChat(key);
- },
- ),
- const SizedBox(width: 6),
- FloatingActionButton.small(
- child: const Icon(Icons.edit),
- onPressed: () {
- final randomChatIndex =
- Random().nextInt(controller.chatListMap.length);
- final chatId =
- controller.chatListMap.keys.elementAt(randomChatIndex);
-
- controller.updateChat(
- chatId,
- (previousChat) {
- final newPinStatus = switch (previousChat.settings.pinStatus) {
- PinStatus.pinned => PinStatus.unpinned,
- PinStatus.unpinned => PinStatus.pinned,
- };
- return previousChat.copyWith(
- settings: previousChat.settings.copyWith(
- pinStatus: newPinStatus,
- pinTime: newPinStatus.isPinned ? DateTime.now() : null,
- ),
- );
- },
- );
- },
- ),
- const SizedBox(width: 6),
- FloatingActionButton.small(
- child: const Icon(Icons.person),
- onPressed: () {
- final randomChatIndex =
- Random().nextInt(controller.chatListMap.length);
- final chatId =
- controller.chatListMap.keys.elementAt(randomChatIndex);
- controller.updateChat(
- chatId,
- (previousChat) => previousChat.copyWith(
- name: 'Updated Chat ${Random().nextInt(100)}',
- ),
- );
- },
- ),
- const SizedBox(width: 6),
- FloatingActionButton.small(
- child: const Icon(Icons.more_horiz),
- onPressed: () {
- final chatId = controller.chatListMap.keys.elementAt(0);
- controller.updateChat(
- chatId,
- (previousChat) => previousChat.copyWith(
- typingUsers: previousChat.typingUsers.isEmpty
- ? {const ChatUser(id: '1', name: 'John Doe')}
- : {},
- ),
- );
- },
- ),
- const SizedBox(width: 6),
- FloatingActionButton.small(
- child: const Icon(Icons.message),
- onPressed: () {
- final randomChatIndex =
- Random().nextInt(controller.chatListMap.length);
- final chatId =
- controller.chatListMap.keys.elementAt(randomChatIndex);
- final randomId = Random().nextInt(100);
- controller.updateChat(
- chatId,
- (previousChat) => previousChat.copyWith(
- lastMessage: Message(
- message: 'Random message $randomId',
- createdAt: DateTime.now(),
- sentBy: previousChat.lastMessage?.sentBy ?? '',
- id: '$randomId',
- ),
- ),
- );
- },
- ),
- ],
- );
- }
-}
diff --git a/example/lib/chat_view_list_screen.dart b/example/lib/chat_view_list_screen.dart
deleted file mode 100644
index c16f1055..00000000
--- a/example/lib/chat_view_list_screen.dart
+++ /dev/null
@@ -1,174 +0,0 @@
-import 'package:chatview/chatview.dart';
-import 'package:flutter/material.dart';
-
-import 'data.dart';
-import 'main.dart';
-
-class ChatViewListScreen extends StatefulWidget {
- const ChatViewListScreen({super.key});
-
- @override
- State createState() => _ChatViewListScreenState();
-}
-
-class _ChatViewListScreenState extends State {
- final _searchController = TextEditingController();
-
- ChatViewListController? _chatListController;
-
- ScrollController? _scrollController;
-
- // Assign the controller in didChangeDependencies
- // to ensure PrimaryScrollController is available.
- @override
- void didChangeDependencies() {
- super.didChangeDependencies();
- _scrollController = PrimaryScrollController.of(context);
- _chatListController ??= ChatViewListController(
- initialChatList: Data.chatList,
- scrollController: _scrollController!,
- disposeOtherResources: false,
- );
- }
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- body: _chatListController == null
- ? const Center(child: CircularProgressIndicator())
- : ChatViewList(
- controller: _chatListController!,
- appbar: ChatViewListAppBar(
- titleText: 'ChatView',
- centerTitle: false,
- scrolledUnderElevation: 0,
- actions: [
- IconButton(
- icon: const Icon(Icons.search),
- onPressed: () {
- // Handle search action
- },
- ),
- ],
- ),
- header: SizedBox(
- height: 60,
- child: ListView(
- padding: const EdgeInsetsGeometry.all(12),
- scrollDirection: Axis.horizontal,
- children: [
- FilterChip.elevated(
- backgroundColor: Colors.grey.shade200,
- label: Text(
- 'All Chats (${_chatListController?.chatListMap.length ?? 0})'),
- onSelected: (bool value) =>
- _chatListController?.clearSearch(),
- ),
- const SizedBox(width: 12),
- FilterChip.elevated(
- backgroundColor: Colors.grey.shade200,
- label: const Text('Pinned Chats'),
- onSelected: (bool value) {
- _chatListController?.setSearchChats(
- _chatListController?.chatListMap.values
- .where((e) => e.settings.pinStatus.isPinned)
- .toList() ??
- [],
- );
- },
- ),
- const SizedBox(width: 12),
- FilterChip.elevated(
- backgroundColor: Colors.grey.shade200,
- label: const Text('Unread Chats'),
- onSelected: (bool value) {
- _chatListController?.setSearchChats(
- _chatListController?.chatListMap.values
- .where((e) => (e.unreadCount ?? 0) > 0)
- .toList() ??
- [],
- );
- },
- ),
- ],
- ),
- ),
- menuConfig: ChatMenuConfig(
- deleteCallback: (chat) =>
- _chatListController?.removeChat(chat.id),
- muteStatusCallback: (result) => _chatListController?.updateChat(
- result.chat.id,
- (previousChat) => previousChat.copyWith(
- settings: previousChat.settings.copyWith(
- muteStatus: result.status,
- ),
- ),
- ),
- pinStatusCallback: (result) => _chatListController?.updateChat(
- result.chat.id,
- (previousChat) => previousChat.copyWith(
- settings: previousChat.settings.copyWith(
- pinStatus: result.status,
- ),
- ),
- ),
- ),
- separatorBuilder: (_, __) => Divider(
- height: 0,
- thickness: 2,
- color: Colors.grey.shade200,
- ),
- tileConfig: ListTileConfig(
- userActiveStatusConfig: const UserActiveStatusConfig(
- alignment: UserActiveStatusAlignment.topRight,
- ),
- lastMessageStatusConfig: LastMessageStatusConfig(
- showStatusFor: (message) => message.sentBy == '2',
- ),
- typingStatusConfig: const TypingStatusConfig(
- showUserNames: true,
- ),
- unreadCountConfig: const UnreadCountConfig(
- style: UnreadCountStyle.ninetyNinePlus,
- ),
- userAvatarConfig: UserAvatarConfig(
- backgroundColor: Colors.red.shade100,
- ),
- onTap: (chat) {
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (context) => ChatScreen(
- chat: chat,
- ),
- ),
- );
- },
- ),
- searchConfig: SearchConfig(
- textEditingController: _searchController,
- debounceDuration: const Duration(milliseconds: 300),
- border: const OutlineInputBorder(
- borderRadius: BorderRadius.all(Radius.circular(10)),
- ),
- onSearch: (value) async {
- if (value.isEmpty) {
- return null;
- }
- final list = _chatListController?.chatListMap.values
- .where((chat) =>
- chat.name.toLowerCase().contains(value.toLowerCase()))
- .toList();
- return list;
- },
- ),
- ),
- );
- }
-
- @override
- void dispose() {
- _chatListController?.dispose();
- _searchController.dispose();
- super.dispose();
- }
-}
diff --git a/example/lib/data.dart b/example/lib/data.dart
index d1e35f97..93dc04c6 100644
--- a/example/lib/data.dart
+++ b/example/lib/data.dart
@@ -4,202 +4,410 @@ class Data {
static const profileImage =
"https://github.com/SimformSolutionsPvtLtd/chatview/blob/main/example/assets/images/simform.png?raw=true";
- static final chatList = [
- ChatViewListItem(
- id: '2',
- name: 'Simform',
- unreadCount: 2,
- imageUrl: Data.profileImage,
- lastMessage: Message(
- id: '12',
- sentBy: '2',
- message: "🤩🤩",
- createdAt: DateTime.now().toUtc(),
- status: MessageStatus.delivered,
+ static List getChatList() {
+ final now = DateTime.now();
+ return [
+ ChatViewListItem(
+ id: '1',
+ name: 'Weekend',
+ imageUrl:
+ 'https://images.unsplash.com/photo-1506744038136-46273834b3fb?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2',
+ chatRoomType: ChatRoomType.group,
+ lastMessage: Message(
+ id: '1',
+ message: 'Who is up for a hike this weekend?',
+ createdAt: now.subtract(const Duration(days: 1)),
+ sentBy: '2',
+ status: MessageStatus.read,
+ ),
+ settings: const ChatSettings(pinStatus: PinStatus.pinned),
),
- settings: ChatSettings(
- pinStatus: PinStatus.pinned,
- pinTime: DateTime.now().toUtc(),
+ ChatViewListItem(
+ id: '2',
+ name: 'Andrius Schneider',
+ imageUrl:
+ 'https://images.pexels.com/photos/1681010/pexels-photo-1681010.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2',
+ typingUsers: {const ChatUser(id: '3', name: 'Andrius')},
+ unreadCount: 2,
+ lastMessage: Message(
+ id: '2',
+ message: 'typing...',
+ createdAt: DateTime(now.year, now.month, now.day, 9, 45),
+ sentBy: '3',
+ status: MessageStatus.delivered,
+ ),
),
- ),
- ChatViewListItem(
- id: '1',
- name: 'Flutter',
- imageUrl: Data.profileImage,
- userActiveStatus: UserActiveStatus.online,
- lastMessage: Message(
- id: '13',
- sentBy: '1',
- message: "https://example.com/image.png",
- messageType: MessageType.image,
- createdAt: DateTime.now().subtract(const Duration(days: 2)).toUtc(),
- status: MessageStatus.delivered,
+ ChatViewListItem(
+ id: '3',
+ name: 'Anna Payet',
+ imageUrl:
+ 'https://images.pexels.com/photos/774909/pexels-photo-774909.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2',
+ lastMessage: Message(
+ id: '3',
+ message: 'Best breakfast ever',
+ createdAt: DateTime(now.year, now.month, now.day, 9, 37),
+ sentBy: '4',
+ messageType: MessageType.image,
+ status: MessageStatus.undelivered,
+ ),
),
- ),
- ChatViewListItem(
- id: '3',
- name: 'Flutter Dev Group',
- imageUrl: Data.profileImage,
- chatRoomType: ChatRoomType.group,
- userActiveStatus: UserActiveStatus.online,
- lastMessage: Message(
- id: '13',
- sentBy: '2',
- message: "https://example.com/image.png",
- messageType: MessageType.image,
- createdAt: DateTime.now().subtract(const Duration(days: 7)).toUtc(),
- status: MessageStatus.delivered,
+ ChatViewListItem(
+ id: '4',
+ name: 'Family',
+ unreadCount: 4,
+ chatRoomType: ChatRoomType.group,
+ imageUrl:
+ 'https://images.pexels.com/photos/1081685/pexels-photo-1081685.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2',
+ lastMessage: Message(
+ id: '4',
+ message: 'See you at the dinner table!',
+ createdAt: now.subtract(const Duration(days: 1)),
+ sentBy: '5',
+ status: MessageStatus.pending,
+ ),
+ ),
+ ChatViewListItem(
+ id: '5',
+ name: 'Maria',
+ imageUrl:
+ 'https://images.pexels.com/photos/1181690/pexels-photo-1181690.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2',
+ lastMessage: Message(
+ id: '5',
+ message: 'It was great to see you! Let\'s catch up again soon',
+ createdAt: now.subtract(const Duration(hours: 1)),
+ sentBy: 'me',
+ status: MessageStatus.read,
+ ),
),
- ),
- for (var i = 4; i < 10; i++)
ChatViewListItem(
- id: i.toString(),
- name: 'Chat $i',
- imageUrl: Data.profileImage,
+ id: '6',
+ name: 'Lunch club!',
+ chatRoomType: ChatRoomType.group,
+ imageUrl:
+ 'https://images.pexels.com/photos/415829/pexels-photo-415829.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2',
lastMessage: Message(
- id: i.toString(),
- sentBy: i % 2 == 0 ? '1' : '2',
- message: "This is message number $i",
- createdAt: DateTime.now()
- .subtract(Duration(days: i, hours: i % 2 == 0 ? i : i * 2))
- .toUtc(),
+ id: '6',
+ message: 'Can we reschedule our lunch to Thursday?',
+ createdAt: now.subtract(const Duration(days: 1)),
+ sentBy: '7',
status: MessageStatus.delivered,
),
+ settings: const ChatSettings(muteStatus: MuteStatus.muted),
),
- ];
+ ChatViewListItem(
+ id: '7',
+ name: "Jasper's market",
+ imageUrl:
+ 'https://images.pexels.com/photos/102104/pexels-photo-102104.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2',
+ unreadCount: 1,
+ lastMessage: Message(
+ id: '7',
+ message: 'It will be ready on Thursday!',
+ createdAt: now.subtract(const Duration(hours: 12)),
+ sentBy: '8',
+ status: MessageStatus.read,
+ ),
+ ),
+ ChatViewListItem(
+ id: '8',
+ name: 'Work',
+ chatRoomType: ChatRoomType.group,
+ imageUrl:
+ 'https://images.pexels.com/photos/3184291/pexels-photo-3184291.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2',
+ lastMessage: Message(
+ id: '8',
+ message: 'Please review the latest project updates.',
+ createdAt: now.subtract(const Duration(days: 2)),
+ sentBy: '9',
+ status: MessageStatus.read,
+ ),
+ ),
+ ChatViewListItem(
+ id: '9',
+ name: 'Evelyn Harper',
+ imageUrl:
+ 'https://images.pexels.com/photos/1239291/pexels-photo-1239291.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2',
+ lastMessage: Message(
+ id: '9',
+ message: 'Let\'s plan our next trip!',
+ createdAt: now.subtract(const Duration(days: 3)),
+ sentBy: '10',
+ status: MessageStatus.read,
+ ),
+ ),
+ ChatViewListItem(
+ id: '10',
+ name: 'Book club',
+ chatRoomType: ChatRoomType.group,
+ imageUrl:
+ 'https://images.pexels.com/photos/159711/book-books-bookstore-book-reading-159711.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2',
+ lastMessage: Message(
+ id: '10',
+ message: 'Next meeting is on Friday at 6 PM.',
+ createdAt: now.subtract(const Duration(days: 4)),
+ sentBy: '11',
+ status: MessageStatus.read,
+ ),
+ ),
+ ];
+ }
- static final messageList = [
- Message(
- id: '1',
- message: "Hi!",
- createdAt: DateTime.now(),
- // userId of who sends the message
- sentBy: '1',
- status: MessageStatus.read,
- ),
- Message(
+ static const currentUser = ChatUser(
+ id: '1',
+ name: 'Flutter',
+ profilePhoto: Data.profileImage,
+ );
+
+ static const otherUsers = [
+ ChatUser(
id: '2',
- message: "Hi!",
- createdAt: DateTime.now(),
- sentBy: '2',
- status: MessageStatus.read,
+ name: 'Simform',
+ profilePhoto: Data.profileImage,
),
- Message(
+ ChatUser(
id: '3',
- message: "We can meet?I am free",
- createdAt: DateTime.now(),
- sentBy: '1',
- status: MessageStatus.read,
+ name: 'Jhon',
+ profilePhoto: Data.profileImage,
),
- Message(
+ ChatUser(
id: '4',
- message: "Can you write the time and place of the meeting?",
- createdAt: DateTime.now(),
- sentBy: '1',
- status: MessageStatus.read,
+ name: 'Mike',
+ profilePhoto: Data.profileImage,
),
- Message(
+ ChatUser(
id: '5',
- message: "That's fine",
- createdAt: DateTime.now(),
- sentBy: '2',
- reaction: Reaction(reactions: ['\u{2764}'], reactedUserIds: ['1']),
- status: MessageStatus.read,
+ name: 'Rich',
+ profilePhoto: Data.profileImage,
),
+ ];
+
+ static getMessageList({bool isExampleOne = true}) => [
+ Message(
+ id: '1',
+ message: "How's it going?",
+ createdAt: DateTime.now().subtract(const Duration(days: 30)),
+ sentBy: '1',
+ status: MessageStatus.read,
+ ),
+ Message(
+ id: '2',
+ message: "I'm doing great, thanks for asking! How about you?",
+ createdAt:
+ DateTime.now().subtract(const Duration(days: 30, minutes: 5)),
+ sentBy: '2',
+ status: MessageStatus.read,
+ reaction: Reaction(reactions: ['\u{1F44D}'], reactedUserIds: ['1']),
+ ),
+ Message(
+ id: '3',
+ message: "I'm good too. Just got back from a vacation.",
+ createdAt: DateTime.now().subtract(const Duration(days: 7)),
+ sentBy: '1',
+ status: MessageStatus.read,
+ ),
+ Message(
+ id: '4',
+ message: "That's awesome! Where did you go?",
+ createdAt:
+ DateTime.now().subtract(const Duration(days: 7, minutes: 5)),
+ sentBy: '2',
+ status: MessageStatus.read,
+ ),
+ Message(
+ id: '5',
+ message: "I went to the mountains. It was beautiful. Here's a pic:",
+ createdAt:
+ DateTime.now().subtract(const Duration(days: 7, minutes: 10)),
+ sentBy: '1',
+ status: MessageStatus.read,
+ ),
+ Message(
+ id: '6',
+ message:
+ "https://images.pexels.com/photos/167699/pexels-photo-167699.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
+ createdAt:
+ DateTime.now().subtract(const Duration(days: 7, minutes: 10)),
+ messageType: MessageType.image,
+ sentBy: '1',
+ status: MessageStatus.read,
+ reaction: Reaction(reactions: ['\u{2764}'], reactedUserIds: ['2']),
+ ),
+ Message(
+ id: '7',
+ message: "Wow, that's breathtaking!",
+ createdAt:
+ DateTime.now().subtract(const Duration(days: 7, minutes: 12)),
+ sentBy: '2',
+ status: MessageStatus.read,
+ replyMessage: const ReplyMessage(
+ message:
+ "https://images.pexels.com/photos/167699/pexels-photo-167699.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
+ replyTo: '1',
+ replyBy: '2',
+ messageId: '6',
+ messageType: MessageType.image,
+ ),
+ ),
+ Message(
+ id: '8',
+ message: "We should go together sometime.",
+ createdAt: DateTime.now().subtract(const Duration(days: 1)),
+ sentBy: '1',
+ status: MessageStatus.read,
+ ),
+ Message(
+ id: '9',
+ message: "I'd love that! Let's plan it.",
+ createdAt:
+ DateTime.now().subtract(const Duration(days: 1, minutes: 5)),
+ sentBy: '2',
+ status: MessageStatus.read,
+ ),
+ Message(
+ id: '10',
+ message: "Are you free this weekend?",
+ createdAt: DateTime.now(),
+ sentBy: '1',
+ status: MessageStatus.read,
+ ),
+ if (isExampleOne)
+ Message(
+ id: '11',
+ message: 'Message unavailable',
+ createdAt: DateTime.now().subtract(const Duration(minutes: 4)),
+ sentBy: '2',
+ status: MessageStatus.delivered,
+ messageType: MessageType.custom,
+ )
+ else
+ Message(
+ id: '18',
+ message: 'Twin Pines Mall - Hill Valley',
+ createdAt: DateTime.now().subtract(const Duration(minutes: 4)),
+ sentBy: '2',
+ status: MessageStatus.delivered,
+ messageType: MessageType.custom,
+ ),
+ Message(
+ id: '12',
+ message: "I think so. Let me check my calendar.",
+ createdAt: DateTime.now().subtract(const Duration(minutes: 2)),
+ sentBy: '2',
+ status: MessageStatus.read,
+ ),
+ Message(
+ id: '13',
+ message: "Yep, I'm free on Saturday.",
+ createdAt: DateTime.now().subtract(const Duration(minutes: 1)),
+ sentBy: '2',
+ status: MessageStatus.read,
+ reaction: Reaction(reactions: ['\u{1F44D}'], reactedUserIds: ['1']),
+ replyMessage: const ReplyMessage(
+ message: "Are you free this weekend?",
+ replyTo: '1',
+ replyBy: '2',
+ messageId: '10',
+ ),
+ ),
+ Message(
+ id: '14',
+ message: "Great! Let's do something fun.",
+ createdAt: DateTime.now().subtract(const Duration(minutes: 2)),
+ sentBy: '1',
+ status: MessageStatus.read,
+ ),
+ Message(
+ id: '200',
+ message:
+ "/data/user/0/com.simform.example/cache/28-09-25-11-07-421922108777129930344.m4a",
+ createdAt: DateTime.now().subtract(const Duration(minutes: 2)),
+ sentBy: '1',
+ messageType: MessageType.voice,
+ status: MessageStatus.read,
+ ),
+ Message(
+ id: '201',
+ message:
+ "/data/user/0/com.simform.example/cache/28-09-25-11-07-421922108777129930344.m4a",
+ createdAt: DateTime.now().subtract(const Duration(minutes: 2)),
+ sentBy: '2',
+ messageType: MessageType.voice,
+ status: MessageStatus.read,
+ ),
+ ];
+
+ static final oldMessageList = [
Message(
- id: '6',
- message: "When to go ?",
- createdAt: DateTime.now(),
- sentBy: '3',
+ id: '101',
+ message: "Hey!",
+ createdAt: DateTime.now().subtract(const Duration(days: 1)),
+ sentBy: '2',
status: MessageStatus.read,
),
Message(
- id: '7',
- message: "I guess Simform will reply",
- createdAt: DateTime.now(),
- sentBy: '4',
+ id: '102',
+ message: "Hi there!",
+ createdAt: DateTime.now().subtract(const Duration(days: 1, minutes: 5)),
+ sentBy: '1',
status: MessageStatus.read,
),
Message(
- id: '8',
- message: "https://bit.ly/3JHS2Wl",
- createdAt: DateTime.now(),
+ id: '103',
+ message: "How was your weekend?",
+ createdAt: DateTime.now().subtract(const Duration(days: 1, minutes: 10)),
sentBy: '2',
- reaction: Reaction(
- reactions: ['\u{2764}', '\u{1F44D}', '\u{1F44D}'],
- reactedUserIds: ['2', '3', '4'],
- ),
status: MessageStatus.read,
- replyMessage: const ReplyMessage(
- message: "Can you write the time and place of the meeting?",
- replyTo: '1',
- replyBy: '2',
- messageId: '4',
- ),
),
Message(
- id: '9',
- message: "Done",
- createdAt: DateTime.now(),
+ id: '104',
+ message: "It was great! I went hiking.",
+ createdAt: DateTime.now().subtract(const Duration(days: 1, minutes: 15)),
sentBy: '1',
status: MessageStatus.read,
- reaction: Reaction(
- reactions: [
- '\u{2764}',
- '\u{2764}',
- '\u{2764}',
- ],
- reactedUserIds: ['2', '3', '4'],
- ),
),
Message(
- id: '10',
- message: "Thank you!!",
+ id: '105',
+ message: "Sounds fun! Did you take any pictures?",
+ createdAt: DateTime.now().subtract(const Duration(minutes: 20)),
+ sentBy: '2',
status: MessageStatus.read,
- createdAt: DateTime.now(),
+ ),
+ Message(
+ id: '106',
+ message: "I'll send it in a moment.",
+ createdAt: DateTime.now().subtract(const Duration(minutes: 17)),
+ messageType: MessageType.text,
sentBy: '1',
- reaction: Reaction(
- reactions: ['\u{2764}', '\u{2764}', '\u{2764}', '\u{2764}'],
- reactedUserIds: ['2', '4', '3', '1'],
- ),
+ status: MessageStatus.read,
),
Message(
- id: '11',
- message: "https://miro.medium.com/max/1000/0*s7of7kWnf9fDg4XM.jpeg",
- createdAt: DateTime.now(),
+ id: '107',
+ message: "https://www.fillmurray.com/640/360",
+ createdAt: DateTime.now().subtract(const Duration(minutes: 16)),
messageType: MessageType.image,
sentBy: '1',
- reaction: Reaction(reactions: ['\u{2764}'], reactedUserIds: ['2']),
status: MessageStatus.read,
),
Message(
- id: '12',
- message: "🤩🤩",
- createdAt: DateTime.now(),
+ id: '108',
+ message: "Haha, nice one!",
+ createdAt: DateTime.now().subtract(const Duration(minutes: 15)),
sentBy: '2',
status: MessageStatus.read,
+ reaction: Reaction(reactions: ['\u{1F602}'], reactedUserIds: ['2']),
),
Message(
- id: '13',
- message: "Check this out",
- createdAt: DateTime.now(),
- replyMessage: const ReplyMessage(
- messageId: '140',
- replyTo: '2',
- replyBy: '1',
- message: "This",
- ),
+ id: '109',
+ message: "What are you up to?",
+ createdAt: DateTime.now().subtract(const Duration(minutes: 10)),
sentBy: '1',
status: MessageStatus.read,
- ),
- for (int i = 14; i <= 19; i++)
- Message(
- id: i.toString(),
- message: "This is message number $i",
- createdAt:
- DateTime.now().subtract(Duration(hours: i % 2 == 0 ? i : i * 2)),
- sentBy: (i % 2 == 0) ? '1' : '2',
- status: MessageStatus.read,
+ replyMessage: const ReplyMessage(
+ message: "Haha, nice one!",
+ replyTo: '1',
+ replyBy: '2',
+ messageId: '9',
),
+ ),
];
}
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 446181b5..58fb0a8e 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -1,8 +1,13 @@
import 'package:chatview/chatview.dart';
-import 'package:example/chat_view_list_screen.dart';
-import 'package:example/data.dart';
-import 'package:example/models/theme.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:intl/intl.dart';
+
+import 'data.dart';
+import 'models/chatview_list_theme.dart';
+import 'models/chatview_theme.dart';
+import 'values/colors.dart';
+import 'values/icons.dart';
void main() {
runApp(const Example());
@@ -17,423 +22,877 @@ class Example extends StatelessWidget {
title: 'Flutter Chat UI Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
- primaryColor: const Color(0xffEE5366),
- colorScheme:
- ColorScheme.fromSwatch(accentColor: const Color(0xffEE5366)),
+ primaryColor: AppColors.instaPurple,
+ colorScheme: ColorScheme.fromSwatch(accentColor: AppColors.instaPurple),
+ ),
+ darkTheme: ThemeData(
+ primaryColor: AppColors.instaPurple,
+ colorScheme: ColorScheme.fromSwatch(accentColor: AppColors.instaPurple),
),
- home: const ChatViewListScreen(),
+ home: const ExampleOneListScreen(),
);
}
}
-class ChatScreen extends StatefulWidget {
- const ChatScreen({required this.chat, super.key});
-
- final ChatViewListItem chat;
+class ExampleOneListScreen extends StatefulWidget {
+ const ExampleOneListScreen({super.key});
@override
- State createState() => _ChatScreenState();
+ State createState() => _ExampleOneListScreenState();
}
-class _ChatScreenState extends State {
- AppTheme theme = LightTheme();
- bool isDarkTheme = false;
- late final ChatController _chatController;
+class _ExampleOneListScreenState extends State {
+ ChatViewListTheme _theme = ChatViewListTheme.uiOneLight;
+ bool _isDarkTheme = false;
+
+ final _searchController = TextEditingController();
+
+ final _chatListController = ChatViewListController(
+ initialChatList: Data.getChatList(),
+ scrollController: ScrollController(),
+ );
@override
- void initState() {
- super.initState();
- _chatController = ChatController(
- initialMessageList: Data.messageList,
- scrollController: ScrollController(),
- currentUser: const ChatUser(
- id: '1',
- name: 'Flutter',
- profilePhoto: Data.profileImage,
- ),
- otherUsers: const [
- ChatUser(
- id: '2',
- name: 'Simform',
- profilePhoto: Data.profileImage,
- ),
- ChatUser(
- id: '3',
- name: 'Jhon',
- profilePhoto: Data.profileImage,
- ),
- ChatUser(
- id: '4',
- name: 'Mike',
- profilePhoto: Data.profileImage,
- ),
- ChatUser(
- id: '5',
- name: 'Rich',
- profilePhoto: Data.profileImage,
+ Widget build(BuildContext context) {
+ return Scaffold(
+ backgroundColor: _theme.backgroundColor,
+ body: SafeArea(
+ child: ChatViewList(
+ controller: _chatListController,
+ backgroundColor: _theme.backgroundColor,
+ header: _headerWidget(),
+ appbar: ChatViewListAppBar(
+ backgroundColor: _theme.backgroundColor,
+ leading: Icon(
+ Icons.arrow_back_ios_rounded,
+ color: _theme.iconColor,
+ ),
+ centerTitle: false,
+ scrolledUnderElevation: 0,
+ titleText: 'ChatViewList',
+ titleTextStyle: TextStyle(
+ fontSize: 20,
+ color: _theme.textColor,
+ fontWeight: FontWeight.bold,
+ ),
+ actions: [
+ SvgPicture.asset(
+ AppIcons.chatDiscoveryAi,
+ colorFilter: ColorFilter.mode(
+ _theme.iconColor,
+ BlendMode.srcIn,
+ ),
+ ),
+ const SizedBox(width: 16),
+ SvgPicture.asset(
+ AppIcons.createPen,
+ colorFilter: ColorFilter.mode(
+ _theme.iconColor,
+ BlendMode.srcIn,
+ ),
+ ),
+ IconButton(
+ onPressed: _onThemeIconTap,
+ icon: Icon(
+ _isDarkTheme ? Icons.light_mode : Icons.dark_mode,
+ color: _theme.iconColor,
+ ),
+ ),
+ ],
+ ),
+ searchConfig: SearchConfig(
+ textEditingController: _searchController,
+ hintText: 'Ask Meta AI or search',
+ hintStyle: TextStyle(
+ fontSize: 16.4,
+ color: _theme.searchText,
+ fontWeight: FontWeight.w400,
+ ),
+ textStyle: TextStyle(
+ fontSize: 16.4,
+ color: _theme.textColor,
+ fontWeight: FontWeight.w400,
+ ),
+ textFieldBackgroundColor: _theme.searchBg,
+ borderRadius: const BorderRadius.all(Radius.circular(30)),
+ prefixIcon: SizedBox.square(
+ dimension: 48,
+ child: Align(child: SvgPicture.asset(AppIcons.ai, width: 24)),
+ ),
+ clearIcon: Icon(Icons.clear, color: _theme.iconColor),
+ contentPadding: const EdgeInsets.symmetric(
+ vertical: 0,
+ horizontal: 16,
+ ),
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ onSearch: (value) {
+ if (value.isEmpty) {
+ _chatListController.clearSearch();
+ return null;
+ }
+
+ List chats =
+ _chatListController.chatListMap.values.toList();
+
+ final list = chats
+ .where((chat) =>
+ chat.name.toLowerCase().contains(value.toLowerCase()))
+ .toList();
+ return list;
+ },
+ ),
+ menuConfig: ChatMenuConfig(
+ deleteCallback: (chat) => _chatListController.removeChat(chat.id),
+ muteStatusCallback: (result) => _chatListController.updateChat(
+ result.chat.id,
+ (previousChat) => previousChat.copyWith(
+ settings: previousChat.settings.copyWith(
+ muteStatus: result.status,
+ ),
+ ),
+ ),
+ pinStatusCallback: (result) => _chatListController.updateChat(
+ result.chat.id,
+ (previousChat) => previousChat.copyWith(
+ settings: previousChat.settings.copyWith(
+ pinStatus: result.status,
+ ),
+ ),
+ ),
+ ),
+ tileConfig: ListTileConfig(
+ showUserActiveStatusIndicator: false,
+ trailingBuilder: (chat) => _customTrailingWidget(chat),
+ userNameBuilder: (chat) => _customUserNameWidget(chat),
+ lastMessageTileBuilder: (chat) => _customLastMessageTile(chat),
+ onTap: (chat) => Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => ExampleOneChatScreen(chat: chat),
+ ),
+ ),
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
+ userAvatarConfig: UserAvatarConfig(
+ radius: 26,
+ backgroundColor: _theme.secondaryBg,
+ ),
+ typingStatusConfig: TypingStatusConfig(
+ textBuilder: (chat) => 'Typing...',
+ textStyle: TextStyle(
+ fontSize: 13,
+ color: _theme.lastMessageText,
+ fontStyle: FontStyle.italic,
+ ),
+ ),
+ ),
),
- ],
+ ),
);
}
@override
void dispose() {
- // ChatController should be disposed to avoid memory leaks
- _chatController.dispose();
+ _chatListController.dispose();
+ _searchController.dispose();
super.dispose();
}
- void _showHideTypingIndicator() {
- _chatController.setTypingIndicator = !_chatController.showTypingIndicator;
+ void _onThemeIconTap() {
+ setState(() {
+ if (_isDarkTheme) {
+ _theme = ChatViewListTheme.uiOneLight;
+ _isDarkTheme = false;
+ } else {
+ _theme = ChatViewListTheme.uiOneDart;
+ _isDarkTheme = true;
+ }
+ });
}
- void receiveMessage() async {
- _chatController.addMessage(
- Message(
- id: DateTime.now().toString(),
- message: 'I will schedule the meeting.',
- createdAt: DateTime.now(),
- sentBy: '2',
- ),
+ Widget _customTrailingWidget(ChatViewListItem chat) {
+ final highlight = (chat.unreadCount ?? 0) > 0;
+ return Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ if (highlight) ...[
+ const CircleAvatar(
+ radius: 4,
+ backgroundColor: AppColors.instaUnreadCountDot,
+ ),
+ const SizedBox(width: 12),
+ ],
+ SvgPicture.asset(
+ AppIcons.camera2,
+ colorFilter: ColorFilter.mode(
+ highlight ? _theme.iconColor : AppColors.instaDarkGrey,
+ BlendMode.srcIn,
+ ),
+ ),
+ ],
);
- await Future.delayed(const Duration(milliseconds: 500));
- _chatController.addReplySuggestions([
- const SuggestionItemData(text: 'Thanks.'),
- const SuggestionItemData(text: 'Thank you very much.'),
- const SuggestionItemData(text: 'Great.')
- ]);
}
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- body: ChatView(
- chatController: _chatController,
- onSendTap: _onSendTap,
- loadMoreData: (direction, message) async {
- await Future.delayed(const Duration(seconds: 1));
- _chatController.loadMoreData(
- [
- Message(
- id: (_chatController.initialMessageList.length + 1).toString(),
- message: '${direction.name} meeting 1.',
- createdAt: direction.isPrevious
- ? DateTime(2015, 21, 00)
- : DateTime.now(),
- sentBy: '2',
+ Widget _headerWidget() {
+ return Padding(
+ padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
+ child: Row(
+ children: [
+ Expanded(
+ child: Text(
+ 'Messages',
+ maxLines: 1,
+ style: TextStyle(
+ fontSize: 16,
+ color: _theme.textColor,
+ fontWeight: FontWeight.bold,
),
- Message(
- id: (_chatController.initialMessageList.length + 2).toString(),
- message: '${direction.name} meeting 2.',
- createdAt: direction.isPrevious
- ? DateTime(2015, 21, 00)
- : DateTime.now(),
- sentBy: '2',
- ),
- if (direction.isNext &&
- _chatController.canRevertToInitialMessageList)
- Data.messageList.first,
- ],
- direction: direction,
- );
- },
- featureActiveConfig: const FeatureActiveConfig(
- lastSeenAgoBuilderVisibility: true,
- receiptsBuilderVisibility: true,
- enableScrollToBottomButton: true,
- enablePagination: true,
- ),
- scrollToBottomButtonConfig: ScrollToBottomButtonConfig(
- backgroundColor: theme.textFieldBackgroundColor,
- border: Border.all(
- color: isDarkTheme ? Colors.transparent : Colors.grey,
+ overflow: TextOverflow.ellipsis,
+ ),
),
- icon: Icon(
- Icons.keyboard_arrow_down_rounded,
- color: theme.themeIconColor,
- weight: 10,
- size: 30,
+ Text(
+ 'Requests',
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ style: TextStyle(color: _theme.searchText),
),
- ),
- chatViewState: ChatViewState.hasMessages,
- chatViewStateConfig: ChatViewStateConfiguration(
- loadingWidgetConfig: ChatViewStateWidgetConfiguration(
- loadingIndicatorColor: theme.outgoingChatBubbleColor,
+ ],
+ ),
+ );
+ }
+
+ Widget _customUserNameWidget(ChatViewListItem chat) {
+ final highlightText = (chat.unreadCount ?? 0) > 0;
+ return Row(
+ children: [
+ Flexible(
+ child: Text(
+ chat.name,
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ style: TextStyle(
+ fontSize: 14,
+ color: highlightText ? _theme.textColor : _theme.lastMessageText,
+ fontWeight: highlightText ? FontWeight.w500 : FontWeight.normal,
+ ),
),
- onReloadButtonTap: () {},
),
- typeIndicatorConfig: TypeIndicatorConfiguration(
- flashingCircleBrightColor: theme.flashingCircleBrightColor,
- flashingCircleDarkColor: theme.flashingCircleDarkColor,
- ),
- appBar: ChatViewAppBar(
- elevation: theme.elevation,
- backGroundColor: theme.appBarColor,
- profilePicture: widget.chat.imageUrl,
- backArrowColor: theme.backArrowColor,
- chatTitle: widget.chat.name,
- chatTitleTextStyle: TextStyle(
- color: theme.appBarTitleTextStyle,
- fontWeight: FontWeight.bold,
- fontSize: 18,
- letterSpacing: 0.25,
- ),
- userStatus:
- widget.chat.userActiveStatus.isOnline ? 'Online' : 'Offline',
- userStatusTextStyle: const TextStyle(color: Colors.grey),
- actions: [
- IconButton(
- onPressed: _onThemeIconTap,
- icon: Icon(
- isDarkTheme
- ? Icons.brightness_4_outlined
- : Icons.dark_mode_outlined,
- color: theme.themeIconColor,
- ),
- ),
- IconButton(
- tooltip: 'Toggle TypingIndicator',
- onPressed: _showHideTypingIndicator,
- icon: Icon(
- Icons.keyboard,
- color: theme.themeIconColor,
- ),
+ if (chat.settings.pinStatus.isPinned) ...[
+ const SizedBox(width: 6),
+ SvgPicture.asset(
+ AppIcons.pinned,
+ width: 16,
+ height: 16,
+ colorFilter: ColorFilter.mode(
+ _theme.lastMessageText,
+ BlendMode.srcIn,
),
- IconButton(
- tooltip: 'Simulate Message receive',
- onPressed: receiveMessage,
- icon: Icon(
- Icons.supervised_user_circle,
- color: theme.themeIconColor,
- ),
+ ),
+ ],
+ ],
+ );
+ }
+
+ Widget _customLastMessageTile(ChatViewListItem chat) {
+ final message = chat.lastMessage;
+ final unreadCount = chat.unreadCount ?? 0;
+ final highlightText = unreadCount > 0;
+ if (message == null) {
+ return const SizedBox.shrink();
+ }
+
+ String prefix = switch (message.status) {
+ MessageStatus.read => 'Seen',
+ MessageStatus.delivered => 'Sent',
+ MessageStatus.undelivered => 'Failed to send.',
+ MessageStatus.pending => 'Sending...',
+ };
+
+ final showDisplayMessage = prefix == 'Seen' || prefix == 'Sent';
+
+ final String display;
+ if (highlightText && unreadCount != 1) {
+ display = '$unreadCount new messages';
+ } else if (!showDisplayMessage) {
+ display = prefix;
+ } else if (message.sentBy == 'me') {
+ display = message.createdAt.getTimestamp(prefix: prefix);
+ } else {
+ display = switch (message.messageType) {
+ MessageType.image => '$prefix a Photo',
+ MessageType.text => message.message,
+ MessageType.voice => '$prefix a Audio',
+ MessageType.custom => '$prefix a Message',
+ };
+ }
+
+ return Row(
+ children: [
+ if (showDisplayMessage && message.messageType != MessageType.text) ...[
+ switch (message.messageType) {
+ MessageType.image => const Icon(Icons.photo, size: 14),
+ MessageType.voice => const Icon(Icons.mic, size: 14),
+ MessageType.text || MessageType.custom => const SizedBox.shrink(),
+ },
+ const SizedBox(width: 5),
+ ],
+ Flexible(
+ child: Text(
+ display,
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ style: TextStyle(
+ fontSize: 14,
+ color: message.status.isUndelivered
+ ? Colors.red
+ : highlightText
+ ? _theme.textColor
+ : _theme.lastMessageText,
+ fontWeight: highlightText ? FontWeight.w500 : FontWeight.normal,
),
- ],
+ ),
),
- chatBackgroundConfig: ChatBackgroundConfiguration(
- messageTimeIconColor: theme.messageTimeIconColor,
- messageTimeTextStyle: TextStyle(color: theme.messageTimeTextColor),
- defaultGroupSeparatorConfig: DefaultGroupSeparatorConfiguration(
- textStyle: TextStyle(
- color: theme.chatHeaderColor,
- fontSize: 17,
+ if (showDisplayMessage) ...[
+ Text(
+ ' · ${message.createdAt.getTimeAgo}',
+ style: TextStyle(
+ fontSize: 14,
+ color: _theme.lastMessageText,
+ fontWeight: FontWeight.normal,
),
),
- backgroundColor: theme.backgroundColor,
- ),
- sendMessageConfig: SendMessageConfiguration(
- imagePickerIconsConfig: ImagePickerIconsConfiguration(
- cameraIconColor: theme.cameraIconColor,
- galleryIconColor: theme.galleryIconColor,
- ),
- replyMessageColor: theme.replyMessageColor,
- defaultSendButtonColor: theme.sendButtonColor,
- replyDialogColor: theme.replyDialogColor,
- replyTitleColor: theme.replyTitleColor,
- textFieldBackgroundColor: theme.textFieldBackgroundColor,
- closeIconColor: theme.closeIconColor,
- textFieldConfig: TextFieldConfiguration(
- onMessageTyping: (status) {
- /// Do with status
- debugPrint(status.toString());
- },
- compositionThresholdTime: const Duration(seconds: 1),
- textStyle: TextStyle(color: theme.textFieldTextColor),
+ ],
+ if (chat.settings.muteStatus.isMuted) ...[
+ const SizedBox(width: 6),
+ Icon(
+ Icons.notifications_off_outlined,
+ size: 16,
+ color: _theme.lastMessageText,
),
- micIconColor: theme.replyMicIconColor,
- voiceRecordingConfiguration: VoiceRecordingConfiguration(
- backgroundColor: theme.waveformBackgroundColor,
- recorderIconColor: theme.recordIconColor,
- waveStyle: WaveStyle(
- showMiddleLine: false,
- waveColor: theme.waveColor ?? Colors.white,
- extendWaveform: true,
- ),
+ ],
+ ],
+ );
+ }
+}
+
+class ExampleOneChatScreen extends StatefulWidget {
+ const ExampleOneChatScreen({required this.chat, super.key});
+
+ final ChatViewListItem chat;
+
+ @override
+ State createState() => _ExampleOneChatScreenState();
+}
+
+class _ExampleOneChatScreenState extends State {
+ ChatViewTheme _theme = ChatViewTheme.uiOneLight;
+ bool _isDarkTheme = false;
+ bool _isTopPaginationCalled = false;
+ bool _isBottomPaginationCalled = false;
+
+ final ChatController _chatController = ChatController(
+ initialMessageList: Data.getMessageList(),
+ scrollController: ScrollController(),
+ currentUser: Data.currentUser,
+ otherUsers: Data.otherUsers,
+ );
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: SafeArea(
+ child: ChatView(
+ chatController: _chatController,
+ onSendTap: _onSendTap,
+ isLastPage: () => _isTopPaginationCalled && _isBottomPaginationCalled,
+ loadMoreData: (direction, message) async {
+ if (direction.isNext) {
+ if (_isBottomPaginationCalled) {
+ return;
+ }
+ _isBottomPaginationCalled = true;
+ } else if (direction.isPrevious) {
+ if (_isTopPaginationCalled) {
+ return;
+ }
+ _isTopPaginationCalled = true;
+ }
+ await Future.delayed(const Duration(seconds: 1));
+ _chatController.loadMoreData(
+ direction.isPrevious
+ ? [
+ Message(
+ id: DateTime.timestamp()
+ .subtract(const Duration(days: 30, minutes: 10))
+ .toIso8601String(),
+ message: "Long time no see!",
+ createdAt: DateTime.now()
+ .subtract(const Duration(days: 30, minutes: 10)),
+ sentBy: '2',
+ status: MessageStatus.read,
+ ),
+ Message(
+ id: DateTime.timestamp()
+ .subtract(const Duration(days: 30, minutes: 5))
+ .toIso8601String(),
+ message: "Indeed! I was about to ping you.",
+ createdAt: DateTime.now()
+ .subtract(const Duration(days: 30, minutes: 5)),
+ sentBy: '1',
+ status: MessageStatus.read,
+ ),
+ ]
+ : [
+ Message(
+ id: '14',
+ message: "How about a movie marathon?",
+ createdAt:
+ DateTime.now().subtract(const Duration(minutes: 1)),
+ sentBy: '2',
+ status: MessageStatus.read,
+ ),
+ Message(
+ id: '15',
+ message: "Sounds great! I'm in. 🎬",
+ createdAt: DateTime.now(),
+ sentBy: '1',
+ status: MessageStatus.read,
+ ),
+ ],
+ direction: direction,
+ );
+ },
+ featureActiveConfig: const FeatureActiveConfig(
+ lastSeenAgoBuilderVisibility: true,
+ receiptsBuilderVisibility: true,
+ enableOtherUserName: false,
+ enableScrollToBottomButton: true,
+ enableOtherUserProfileAvatar: true,
+ enablePagination: true,
),
- ),
- chatBubbleConfig: ChatBubbleConfiguration(
- outgoingChatBubbleConfig: ChatBubble(
- linkPreviewConfig: LinkPreviewConfiguration(
- backgroundColor: theme.linkPreviewOutgoingChatColor,
- bodyStyle: theme.outgoingChatLinkBodyStyle,
- titleStyle: theme.outgoingChatLinkTitleStyle,
- ),
- receiptsWidgetConfig:
- const ReceiptsWidgetConfig(showReceiptsIn: ShowReceiptsIn.all),
- color: theme.outgoingChatBubbleColor,
- ),
- inComingChatBubbleConfig: ChatBubble(
- linkPreviewConfig: LinkPreviewConfiguration(
- linkStyle: TextStyle(
- color: theme.inComingChatBubbleTextColor,
- decoration: TextDecoration.underline,
+ scrollToBottomButtonConfig: const ScrollToBottomButtonConfig(
+ padding: EdgeInsets.only(bottom: 8, right: 12),
+ insidePadding: EdgeInsets.all(10),
+ alignment: ScrollButtonAlignment.right,
+ icon: Icon(Icons.arrow_downward_rounded),
+ border: Border.fromBorderSide(BorderSide.none),
+ boxShadow: [
+ BoxShadow(
+ blurRadius: 8,
+ offset: Offset(0, 4),
+ color: Colors.black26,
),
- backgroundColor: theme.linkPreviewIncomingChatColor,
- bodyStyle: theme.incomingChatLinkBodyStyle,
- titleStyle: theme.incomingChatLinkTitleStyle,
- ),
- textStyle: TextStyle(color: theme.inComingChatBubbleTextColor),
- onMessageRead: (message) {
- /// send your message reciepts to the other client
- debugPrint('Message Read');
- },
- senderNameTextStyle:
- TextStyle(color: theme.inComingChatBubbleTextColor),
- color: theme.inComingChatBubbleColor,
+ ],
),
- ),
- replyPopupConfig: ReplyPopupConfiguration(
- backgroundColor: theme.replyPopupColor,
- buttonTextStyle: TextStyle(color: theme.replyPopupButtonColor),
- topBorderColor: theme.replyPopupTopBorderColor,
- ),
- reactionPopupConfig: ReactionPopupConfiguration(
- shadow: BoxShadow(
- color: isDarkTheme ? Colors.black54 : Colors.grey.shade400,
- blurRadius: 20,
+ chatViewState: ChatViewState.hasMessages,
+ typeIndicatorConfig: TypeIndicatorConfiguration(
+ customIndicator: _customTypingIndicator(),
),
- backgroundColor: theme.reactionPopupColor,
- ),
- messageConfig: MessageConfiguration(
- messageReactionConfig: MessageReactionConfiguration(
- backgroundColor: theme.messageReactionBackGroundColor,
- borderColor: theme.messageReactionBackGroundColor,
- reactedUserCountTextStyle:
- TextStyle(color: theme.inComingChatBubbleTextColor),
- reactionCountTextStyle:
- TextStyle(color: theme.inComingChatBubbleTextColor),
- reactionsBottomSheetConfig: ReactionsBottomSheetConfiguration(
- backgroundColor: theme.backgroundColor,
- reactedUserTextStyle: TextStyle(
- color: theme.inComingChatBubbleTextColor,
- ),
- reactionWidgetDecoration: BoxDecoration(
- color: theme.inComingChatBubbleColor,
- boxShadow: [
- BoxShadow(
- color: isDarkTheme ? Colors.black12 : Colors.grey.shade200,
- offset: const Offset(0, 20),
- blurRadius: 40,
- )
- ],
- borderRadius: BorderRadius.circular(10),
- ),
+ appBar: ChatViewAppBar(
+ elevation: 0,
+ chatTitle: widget.chat.name,
+ leading: IconButton(
+ onPressed: Navigator.of(context).maybePop,
+ icon: Icon(Icons.arrow_back_ios, color: _theme.iconColor),
),
- ),
- imageMessageConfig: ImageMessageConfiguration(
- margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 15),
- shareIconConfig: ShareIconConfiguration(
- defaultIconBackgroundColor: theme.shareIconBackgroundColor,
- defaultIconColor: theme.shareIconColor,
+ chatTitleTextStyle: TextStyle(
+ fontSize: 18,
+ color: _theme.titleColor,
+ fontWeight: FontWeight.w600,
),
- ),
- ),
- profileCircleConfig: const ProfileCircleConfiguration(
- profileImageUrl: Data.profileImage,
- ),
- repliedMessageConfig: RepliedMessageConfiguration(
- loadOldReplyMessage: (id) async {
- _chatController.replaceMessageList(
- [
- Message(
- id: '123',
- message: 'What about the meeting.',
- createdAt: DateTime(2015, 6, 21),
- sentBy: '1',
+ profilePicture: widget.chat.imageUrl,
+ backGroundColor: _theme.backgroundColor,
+ userStatus: '@${widget.chat.name}',
+ userStatusTextStyle: TextStyle(
+ fontSize: 13,
+ color: _theme.titleColor,
+ ),
+ actions: [
+ IconButton(
+ onPressed: () {},
+ icon: SvgPicture.asset(
+ AppIcons.chatDiscoveryAi,
+ colorFilter: ColorFilter.mode(
+ _theme.iconColor,
+ BlendMode.srcIn,
+ ),
),
- Message(
- id: '124',
- message: 'I will ignore the meeting.',
- createdAt: DateTime(2015, 6, 21),
- sentBy: '2',
+ ),
+ IconButton(
+ onPressed: () {},
+ icon: SvgPicture.asset(
+ AppIcons.phone,
+ colorFilter: ColorFilter.mode(
+ _theme.iconColor,
+ BlendMode.srcIn,
+ ),
),
- Message(
- id: '125',
- message: 'I will join the meeting.',
- createdAt: DateTime(2015, 6, 20),
- sentBy: '3',
+ ),
+ IconButton(
+ // Handle video call
+ onPressed: () {},
+ icon: SvgPicture.asset(
+ AppIcons.video,
+ colorFilter: ColorFilter.mode(
+ _theme.iconColor,
+ BlendMode.srcIn,
+ ),
),
- Message(
- id: '126',
- message: 'Such a boring meeting.',
- createdAt: DateTime(2015, 6, 21),
- sentBy: '1',
+ ),
+ PopupMenuButton(
+ icon: Icon(
+ Icons.more_vert_rounded,
+ color: _theme.iconColor,
),
- Message(
- id: '127',
- message: 'Stop talking about the meeting.',
- createdAt: DateTime(2015, 6, 21),
- sentBy: '4',
+ itemBuilder: (context) => [
+ const PopupMenuItem(
+ value: 'toggle_typing_indicator',
+ child: Text('Toggle TypingIndicator'),
+ ),
+ const PopupMenuItem(
+ value: 'simulate_message_receive',
+ child: Text('Simulate Message receive'),
+ ),
+ PopupMenuItem(
+ value: 'dark_theme',
+ child: Text(' ${_isDarkTheme ? 'Light' : 'Dark'} Mode'),
+ ),
+ ],
+ onSelected: (value) {
+ switch (value) {
+ case 'toggle_typing_indicator':
+ _showHideTypingIndicator();
+ case 'simulate_message_receive':
+ receiveMessage();
+ case 'dark_theme':
+ _onThemeIconTap();
+ }
+ },
+ ),
+ const SizedBox(width: 12),
+ ],
+ ),
+ chatBackgroundConfig: ChatBackgroundConfiguration(
+ backgroundColor: _theme.backgroundColor,
+ groupSeparatorBuilder: (separator) =>
+ _customSeparatorWidget(separator),
+ ),
+ sendMessageConfig: SendMessageConfiguration(
+ closeIconColor: _theme.iconColor,
+ replyTitleColor: _theme.textColor,
+ replyMessageColor: _theme.textColor,
+ replyDialogColor: _theme.backgroundColor,
+ defaultSendButtonColor: Colors.white,
+ textFieldBackgroundColor: _theme.textField,
+ voiceRecordingConfiguration: VoiceRecordingConfiguration(
+ recorderIconColor: _theme.iconColor,
+ waveStyle: WaveStyle(
+ extendWaveform: true,
+ showMiddleLine: false,
+ waveColor: _theme.iconColor,
+ durationLinesColor: AppColors.black20,
+ backgroundColor: Colors.transparent,
+ scaleFactor: 60,
+ waveThickness: 3,
+ spacing: 4,
+ ),
+ ),
+ sendButtonStyle: IconButton.styleFrom(
+ backgroundColor: AppColors.instaPurple,
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ ),
+ textFieldConfig: TextFieldConfiguration(
+ hintText: 'Message...',
+ hideLeadingActionsOnType: false,
+ onMessageTyping: (status) {
+ /// Do with status
+ debugPrint(status.toString());
+ },
+ compositionThresholdTime: const Duration(seconds: 1),
+ textStyle: TextStyle(color: _theme.textColor),
+ contentPadding: const EdgeInsets.symmetric(horizontal: 12),
+ leadingActions: (context, controller) =>
+ controller.text.trim().isEmpty
+ ? [
+ CameraActionButton(
+ icon: const Icon(
+ Icons.camera_alt_rounded,
+ color: Colors.white,
+ ),
+ style: IconButton.styleFrom(
+ backgroundColor: AppColors.instaPurple,
+ ),
+ onPressed: (path, replyMessage) {
+ if (path?.isEmpty ?? true) return;
+ _chatController.addMessage(
+ Message(
+ id: DateTime.now()
+ .millisecondsSinceEpoch
+ .toString(),
+ message: path!,
+ createdAt: DateTime.now(),
+ messageType: MessageType.image,
+ sentBy: _chatController.currentUser.id,
+ replyMessage:
+ replyMessage ?? const ReplyMessage(),
+ ),
+ );
+ _chatController.addMessage(
+ Message(
+ message: controller.text,
+ id: DateTime.now()
+ .millisecondsSinceEpoch
+ .toString(),
+ createdAt: DateTime.now(),
+ sentBy: _chatController.currentUser.id,
+ replyMessage:
+ replyMessage ?? const ReplyMessage(),
+ ),
+ );
+ },
+ ),
+ ]
+ : [
+ IconButton(
+ icon: const Icon(
+ Icons.search_rounded,
+ color: Colors.white,
+ ),
+ style: IconButton.styleFrom(
+ backgroundColor: AppColors.instaPurple,
+ ),
+ onPressed: () =>
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('Search button pressed')),
+ ),
+ ),
+ ],
+ trailingActions: (context, controller) => [
+ GalleryActionButton(
+ icon: Icon(
+ Icons.photo_rounded,
+ size: 30,
+ color: _theme.iconColor,
+ ),
+ onPressed: (path, replyMessage) {
+ if (path?.isEmpty ?? true) return;
+ _chatController.addMessage(
+ Message(
+ id: DateTime.now().millisecondsSinceEpoch.toString(),
+ message: path!,
+ createdAt: DateTime.now(),
+ messageType: MessageType.image,
+ sentBy: _chatController.currentUser.id,
+ replyMessage: replyMessage ?? const ReplyMessage(),
+ ),
+ );
+ },
),
- Message(
- id: '140',
- message: 'Stop talking about the meeting.',
- createdAt: DateTime(2015, 6, 21),
- sentBy: '4',
+ EmojiPickerActionButton(
+ context: context,
+ onPressed: (emoji, replyMessage) {
+ if (emoji?.isEmpty ?? true) return;
+ controller.text = controller.text += emoji!;
+ _chatController.addMessage(
+ Message(
+ message: controller.text,
+ id: DateTime.now().millisecondsSinceEpoch.toString(),
+ createdAt: DateTime.now(),
+ sentBy: _chatController.currentUser.id,
+ replyMessage: replyMessage ?? const ReplyMessage(),
+ ),
+ );
+ },
+ icon: SvgPicture.asset(
+ AppIcons.sticker,
+ width: 30,
+ height: 30,
+ colorFilter: ColorFilter.mode(
+ _theme.iconColor,
+ BlendMode.srcIn,
+ ),
+ ),
),
- Message(
- id: '128',
- message: 'Stop talking about the meeting.',
- createdAt: DateTime(2015, 6, 21),
- sentBy: '4',
+ IconButton(
+ onPressed: () => ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(content: Text('Add attachment')),
+ ),
+ icon: Icon(
+ Icons.add_circle_outline_rounded,
+ size: 30,
+ color: _theme.iconColor,
+ ),
),
- Message(
- id: '129',
- message: 'Stop talking about the meeting.',
- createdAt: DateTime(2015, 6, 21),
- sentBy: '4',
+ ],
+ ),
+ ),
+ chatBubbleConfig: ChatBubbleConfiguration(
+ outgoingChatBubbleConfig: const ChatBubble(
+ linkPreviewConfig: LinkPreviewConfiguration(
+ backgroundColor: Colors.white12,
+ linkStyle: TextStyle(
+ fontSize: 16,
+ color: Colors.white,
+ ),
+ ),
+ color: AppColors.instaPurple,
+ textStyle: TextStyle(color: Colors.white, fontSize: 16),
+ receiptsWidgetConfig: ReceiptsWidgetConfig(
+ showReceiptsIn: ShowReceiptsIn.lastMessage,
+ ),
+ ),
+ inComingChatBubbleConfig: ChatBubble(
+ linkPreviewConfig: LinkPreviewConfiguration(
+ backgroundColor: Colors.white12,
+ linkStyle: TextStyle(
+ fontSize: 16,
+ color: _theme.textColor,
),
- Message(
- id: '130',
- message: 'Stop talking about the meeting.',
- createdAt: DateTime(2015, 6, 21),
- sentBy: '4',
+ ),
+ color: _theme.incomingBubble,
+ textStyle: TextStyle(color: _theme.textColor, fontSize: 16),
+ onMessageRead: (message) {
+ /// send your message reciepts to the other client
+ debugPrint('Message Read');
+ },
+ ),
+ ),
+ reactionPopupConfig: const ReactionPopupConfiguration(
+ backgroundColor: Colors.white,
+ shadow: BoxShadow(
+ blurRadius: 8,
+ offset: Offset(0, 4),
+ color: Colors.black26,
+ ),
+ ),
+ messageConfig: MessageConfiguration(
+ voiceMessageConfig: VoiceMessageConfiguration(
+ margin: EdgeInsets.zero,
+ padding: EdgeInsets.zero,
+ playIcon: (isMessageBySender) => Icon(
+ Icons.play_arrow_rounded,
+ size: 24,
+ color: isMessageBySender ? Colors.white : _theme.iconColor,
+ ),
+ pauseIcon: (isMessageBySender) => Icon(
+ Icons.pause_rounded,
+ size: 24,
+ color: isMessageBySender ? Colors.white : _theme.iconColor,
+ ),
+ inComingPlayerWaveStyle: PlayerWaveStyle(
+ liveWaveColor: _theme.iconColor,
+ fixedWaveColor: AppColors.black20,
+ backgroundColor: Colors.transparent,
+ scaleFactor: 60,
+ waveThickness: 3,
+ spacing: 4,
+ ),
+ outgoingPlayerWaveStyle: PlayerWaveStyle(
+ liveWaveColor: _theme.iconColor,
+ fixedWaveColor: AppColors.white20,
+ backgroundColor: Colors.transparent,
+ scaleFactor: 60,
+ waveThickness: 3,
+ spacing: 4,
+ ),
+ ),
+ messageReactionConfig: MessageReactionConfiguration(
+ backgroundColor: _theme.incomingBubble,
+ borderColor: _theme.backgroundColor,
+ borderWidth: 2,
+ reactionsBottomSheetConfig: ReactionsBottomSheetConfiguration(
+ backgroundColor: _theme.backgroundColor,
+ reactedUserTextStyle: TextStyle(
+ color: _theme.textColor,
),
- Message(
- id: '131',
- message: 'Stop talking about the meeting.',
- createdAt: DateTime(2015, 6, 21),
- sentBy: '4',
+ reactionWidgetDecoration: BoxDecoration(
+ color: _theme.incomingBubble,
+ borderRadius: const BorderRadius.all(Radius.circular(12)),
),
- ],
- );
- },
- backgroundColor: theme.repliedMessageColor,
- verticalBarColor: theme.verticalBarColor,
- repliedMsgAutoScrollConfig: RepliedMsgAutoScrollConfig(
- enableHighlightRepliedMsg: true,
- highlightColor: Colors.pinkAccent.shade100,
- highlightScale: 1.1,
- ),
- textStyle: const TextStyle(
- color: Colors.white,
- fontWeight: FontWeight.bold,
- letterSpacing: 0.25,
- ),
- replyTitleTextStyle: TextStyle(color: theme.repliedTitleTextColor),
- ),
- swipeToReplyConfig: SwipeToReplyConfiguration(
- replyIconColor: theme.swipeToReplyIconColor,
- ),
- replySuggestionsConfig: ReplySuggestionsConfig(
- itemConfig: SuggestionItemConfig(
- decoration: BoxDecoration(
- color: theme.textFieldBackgroundColor,
- borderRadius: BorderRadius.circular(8),
- border: Border.all(
- color: theme.outgoingChatBubbleColor ?? Colors.white,
),
),
- textStyle: TextStyle(
- color: isDarkTheme ? Colors.white : Colors.black,
+ customMessageBuilder: (message) {
+ if (message.message == 'Message unavailable') {
+ return _buildUnavailableMessage(message);
+ }
+ return const SizedBox.shrink();
+ },
+ ),
+ profileCircleConfig: const ProfileCircleConfiguration(
+ padding: EdgeInsets.only(right: 4),
+ profileImageUrl: Data.profileImage,
+ ),
+ repliedMessageConfig: RepliedMessageConfiguration(
+ backgroundColor: _theme.replyBg,
+ verticalBarColor: _theme.verticalDivider,
+ textStyle: TextStyle(color: _theme.replyText),
+ replyTitleTextStyle: TextStyle(
+ fontSize: 12,
+ color: _theme.titleColor,
+ ),
+ loadOldReplyMessage: (id) async => {
+ // Implement logic to fetch old replied message with surrounding context using message ID
+ },
+ repliedMsgAutoScrollConfig: RepliedMsgAutoScrollConfig(
+ enableHighlightRepliedMsg: true,
+ highlightScale: 1.1,
+ highlightColor: Colors.grey.shade300,
+ ),
+ ),
+ swipeToReplyConfig: SwipeToReplyConfiguration(
+ replyIconColor: _theme.iconColor,
+ replyIconBackgroundColor: _theme.backgroundColor,
+ replyIconProgressRingColor: _theme.incomingBubble,
+ ),
+ replySuggestionsConfig: ReplySuggestionsConfig(
+ itemConfig: SuggestionItemConfig(
+ padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
+ decoration: BoxDecoration(
+ color: _theme.incomingBubble,
+ borderRadius: BorderRadius.circular(30),
+ border: Border.all(color: _theme.incomingBubble),
+ ),
+ textStyle: TextStyle(color: _theme.textColor),
+ ),
+ onTap: (item) => _onSendTap(
+ item.text,
+ const ReplyMessage(),
+ MessageType.text,
),
),
- onTap: (item) =>
- _onSendTap(item.text, const ReplyMessage(), MessageType.text),
),
),
);
}
+ @override
+ void dispose() {
+ // ChatController should be disposed to avoid memory leaks
+ _chatController.dispose();
+ super.dispose();
+ }
+
+ void _showHideTypingIndicator() {
+ _chatController.setTypingIndicator = !_chatController.showTypingIndicator;
+ }
+
+ void receiveMessage() async {
+ _chatController.addMessage(
+ Message(
+ id: DateTime.now().toString(),
+ message: 'I will schedule the meeting.',
+ createdAt: DateTime.now(),
+ sentBy: '2',
+ ),
+ );
+ await Future.delayed(const Duration(milliseconds: 500));
+ _chatController.addReplySuggestions([
+ const SuggestionItemData(text: 'Thanks.'),
+ const SuggestionItemData(text: 'Thank you very much.'),
+ const SuggestionItemData(text: 'Great.')
+ ]);
+ }
+
void _onSendTap(
String message,
ReplyMessage replyMessage,
@@ -464,13 +923,183 @@ class _ChatScreenState extends State {
void _onThemeIconTap() {
setState(() {
- if (isDarkTheme) {
- theme = LightTheme();
- isDarkTheme = false;
+ if (_isDarkTheme) {
+ _theme = ChatViewTheme.uiOneLight;
+ _isDarkTheme = false;
} else {
- theme = DarkTheme();
- isDarkTheme = true;
+ _theme = ChatViewTheme.uiOneDark;
+ _isDarkTheme = true;
}
});
}
+
+ Widget _buildUnavailableMessage(Message message) {
+ return Container(
+ constraints:
+ BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.75),
+ padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
+ margin: EdgeInsets.fromLTRB(
+ 5, 0, 6, message.reaction.reactions.isNotEmpty ? 15 : 2),
+ decoration: BoxDecoration(
+ color: _theme.incomingBubble,
+ borderRadius: const BorderRadius.all(Radius.circular(18)),
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ message.message,
+ style: TextStyle(
+ fontSize: 16,
+ color: _theme.textColor,
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ const SizedBox(height: 4),
+ Text(
+ 'This content may have been deleted by its owner or hidden by their privacy settings.',
+ style: TextStyle(color: _theme.textColor, fontSize: 15),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _customSeparatorWidget(String separator) {
+ final date = DateTime.tryParse(separator);
+ if (date == null) {
+ return const SizedBox.shrink();
+ }
+ String separatorDate;
+ final now = DateTime.now();
+ if (date.day == now.day &&
+ date.month == now.month &&
+ date.year == now.year) {
+ separatorDate = DateFormat('h:mm a').format(date);
+ } else if (date.day == now.day - 1 &&
+ date.month == now.month &&
+ date.year == now.year) {
+ separatorDate = 'Yesterday at ${DateFormat('h:mm a').format(date)}';
+ } else if (date.isAfter(now.subtract(const Duration(days: 7)))) {
+ separatorDate = DateFormat('EEE h:mm a').format(date);
+ } else {
+ separatorDate = DateFormat('d MMM AT h:mm a').format(date);
+ }
+ return Align(
+ heightFactor: 2,
+ child: Text(
+ separatorDate.toUpperCase(),
+ style: TextStyle(
+ fontSize: 13,
+ fontWeight: FontWeight.w500,
+ color: _theme.titleColor,
+ ),
+ ),
+ );
+ }
+
+ Widget _customTypingIndicator() {
+ return Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ if (_chatController.otherUsers.firstOrNull?.profilePhoto
+ case final image?) ...[
+ CircleAvatar(
+ radius: 16,
+ backgroundImage: NetworkImage(image),
+ backgroundColor: _theme.incomingBubble,
+ ),
+ ],
+ Container(
+ margin: const EdgeInsets.only(left: 8),
+ decoration: BoxDecoration(
+ color: _theme.incomingBubble,
+ borderRadius: const BorderRadius.all(Radius.circular(30)),
+ ),
+ padding: const EdgeInsets.symmetric(
+ vertical: 18,
+ horizontal: 14,
+ ),
+ child: const Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ CircleAvatar(
+ radius: 2.75,
+ backgroundColor: AppColors.instaDarkGrey,
+ ),
+ SizedBox(width: 3),
+ CircleAvatar(
+ radius: 2.75,
+ backgroundColor: AppColors.instaDarkGrey,
+ ),
+ SizedBox(width: 3),
+ CircleAvatar(
+ radius: 2.75,
+ backgroundColor: AppColors.instaDarkGrey,
+ ),
+ ],
+ ),
+ ),
+ ],
+ );
+ }
+}
+
+extension on DateTime {
+ String get getTimeAgo {
+ final now = DateTime.now();
+ final diff = now.difference(this);
+
+ if (diff.inSeconds < 60) return 'now';
+ if (diff.inMinutes < 60) return '${diff.inMinutes}m';
+ if (diff.inHours < 24) return '${diff.inHours}h';
+ if (diff.inDays < 7) return '${diff.inDays}d';
+ if (diff.inDays < 30) {
+ final weeks = (diff.inDays / 7).floor();
+ return '${weeks}w';
+ }
+ return now.year == year
+ ? DateFormat('MMM d').format(this) // e.g. Aug 5
+ : DateFormat('MMM d, y').format(this); // e.g. Aug 5, 2023;
+ }
+
+ String getTimestamp({required String prefix}) {
+ String timeStamp;
+ if (isNow) {
+ timeStamp = 'just Now';
+ } else {
+ final difference = DateTime.now().difference(this);
+ // if the difference is 1 day, show hours wise
+ if (difference.inDays == 0) {
+ if (difference.inHours > 0) {
+ timeStamp = '${difference.inHours}h ago';
+ } else if (difference.inMinutes > 0) {
+ timeStamp = '${difference.inMinutes}m ago';
+ } else {
+ timeStamp = 'now';
+ }
+ // if less than 7 days, show days wise
+ } else if (isLast7Days) {
+ final day = DateFormat('EEEE').format(this);
+ timeStamp = 'on $day';
+ } else {
+ timeStamp = '';
+ }
+ }
+ return '$prefix $timeStamp';
+ }
+
+ bool get isNow {
+ final now = DateTime.now();
+ return year == now.year &&
+ month == now.month &&
+ day == now.day &&
+ hour == now.hour &&
+ minute == now.minute;
+ }
+
+ bool get isLast7Days {
+ final now = DateTime.now();
+ return now.difference(DateTime(year, month, day)).inDays < 7;
+ }
}
diff --git a/example/lib/models/chatview_list_theme.dart b/example/lib/models/chatview_list_theme.dart
new file mode 100644
index 00000000..29f9ac64
--- /dev/null
+++ b/example/lib/models/chatview_list_theme.dart
@@ -0,0 +1,41 @@
+import 'package:flutter/material.dart';
+
+class ChatViewListTheme {
+ const ChatViewListTheme({
+ required this.textColor,
+ required this.iconColor,
+ required this.searchBg,
+ required this.searchText,
+ required this.lastMessageText,
+ required this.backgroundColor,
+ required this.secondaryBg,
+ });
+
+ final Color textColor;
+ final Color iconColor;
+ final Color searchBg;
+ final Color searchText;
+ final Color lastMessageText;
+ final Color backgroundColor;
+ final Color secondaryBg;
+
+ static const ChatViewListTheme uiOneDart = ChatViewListTheme(
+ iconColor: Colors.white,
+ textColor: Colors.white,
+ searchBg: Color(0xff26292E),
+ searchText: Color(0xffA7ADB6),
+ lastMessageText: Color(0xff74787F),
+ backgroundColor: Color(0xff0B1014),
+ secondaryBg: Color(0xff26292E),
+ );
+
+ static const ChatViewListTheme uiOneLight = ChatViewListTheme(
+ iconColor: Colors.black,
+ textColor: Colors.black,
+ searchBg: Color(0xffF3F4F7),
+ searchText: Color(0xff5D636E),
+ lastMessageText: Colors.black,
+ backgroundColor: Color(0xffFEFFFE),
+ secondaryBg: Color(0xffF3F5F7),
+ );
+}
diff --git a/example/lib/models/chatview_theme.dart b/example/lib/models/chatview_theme.dart
new file mode 100644
index 00000000..de7edaf4
--- /dev/null
+++ b/example/lib/models/chatview_theme.dart
@@ -0,0 +1,49 @@
+import 'package:flutter/material.dart';
+
+class ChatViewTheme {
+ const ChatViewTheme({
+ required this.textColor,
+ required this.titleColor,
+ required this.iconColor,
+ required this.backgroundColor,
+ required this.textField,
+ required this.incomingBubble,
+ required this.verticalDivider,
+ required this.replyBg,
+ required this.replyText,
+ });
+
+ final Color textColor;
+ final Color titleColor;
+ final Color iconColor;
+ final Color backgroundColor;
+ final Color textField;
+ final Color incomingBubble;
+ final Color verticalDivider;
+ final Color replyBg;
+ final Color replyText;
+
+ static const ChatViewTheme uiOneDark = ChatViewTheme(
+ iconColor: Colors.white,
+ textColor: Colors.white,
+ titleColor: Color(0xff6B7079),
+ backgroundColor: Color(0xff0C1014),
+ textField: Color(0xff1C1E1F),
+ incomingBubble: Color(0xff26292E),
+ verticalDivider: Color(0xff191C21),
+ replyBg: Color(0xff191C21),
+ replyText: Color(0xff999B9D),
+ );
+
+ static const ChatViewTheme uiOneLight = ChatViewTheme(
+ iconColor: Colors.black,
+ textColor: Colors.black,
+ titleColor: Colors.black,
+ backgroundColor: Colors.white,
+ textField: Color(0xffF7F7F7),
+ incomingBubble: Color(0xffF3F5F7),
+ verticalDivider: Color(0xffF3F5F7),
+ replyBg: Color(0xffF9FAFB),
+ replyText: Color(0xff6D7073),
+ );
+}
diff --git a/example/lib/models/theme.dart b/example/lib/models/theme.dart
deleted file mode 100644
index 53d9932d..00000000
--- a/example/lib/models/theme.dart
+++ /dev/null
@@ -1,319 +0,0 @@
-import 'package:flutter/material.dart';
-
-class AppTheme {
- final Color? appBarColor;
- final Color? backArrowColor;
- final Color? backgroundColor;
- final Color? replyDialogColor;
- final Color? replyTitleColor;
- final Color? textFieldBackgroundColor;
-
- final Color? outgoingChatBubbleColor;
-
- final Color? inComingChatBubbleColor;
-
- final Color? inComingChatBubbleTextColor;
- final Color? repliedMessageColor;
- final Color? repliedTitleTextColor;
- final Color? textFieldTextColor;
-
- final Color? closeIconColor;
- final Color? shareIconBackgroundColor;
-
- final Color? sendButtonColor;
- final Color? cameraIconColor;
- final Color? galleryIconColor;
- final Color? recordIconColor;
- final Color? stopIconColor;
- final Color? swipeToReplyIconColor;
- final Color? replyMessageColor;
- final Color? appBarTitleTextStyle;
- final Color? messageReactionBackGroundColor;
- final Color? messageTimeIconColor;
- final Color? messageTimeTextColor;
- final Color? reactionPopupColor;
- final Color? replyPopupColor;
- final Color? replyPopupButtonColor;
- final Color? replyPopupTopBorderColor;
- final Color? reactionPopupTitleColor;
- final Color? flashingCircleDarkColor;
- final Color? flashingCircleBrightColor;
- final Color? waveformBackgroundColor;
- final Color? waveColor;
- final Color? replyMicIconColor;
- final Color? messageReactionBorderColor;
-
- final Color? verticalBarColor;
- final Color? chatHeaderColor;
- final Color? themeIconColor;
- final Color? shareIconColor;
- final double? elevation;
- final Color? linkPreviewIncomingChatColor;
- final Color? linkPreviewOutgoingChatColor;
- final TextStyle? linkPreviewIncomingTitleStyle;
- final TextStyle? linkPreviewOutgoingTitleStyle;
- final TextStyle? incomingChatLinkTitleStyle;
- final TextStyle? outgoingChatLinkTitleStyle;
- final TextStyle? outgoingChatLinkBodyStyle;
- final TextStyle? incomingChatLinkBodyStyle;
-
- AppTheme({
- this.cameraIconColor,
- this.galleryIconColor,
- this.flashingCircleDarkColor,
- this.flashingCircleBrightColor,
- this.outgoingChatLinkBodyStyle,
- this.incomingChatLinkBodyStyle,
- this.incomingChatLinkTitleStyle,
- this.outgoingChatLinkTitleStyle,
- this.linkPreviewOutgoingChatColor,
- this.linkPreviewIncomingChatColor,
- this.linkPreviewIncomingTitleStyle,
- this.linkPreviewOutgoingTitleStyle,
- this.repliedTitleTextColor,
- this.swipeToReplyIconColor,
- this.textFieldTextColor,
- this.reactionPopupColor,
- this.replyPopupButtonColor,
- this.replyPopupTopBorderColor,
- this.reactionPopupTitleColor,
- this.appBarColor,
- this.backArrowColor,
- this.backgroundColor,
- this.replyDialogColor,
- this.replyTitleColor,
- this.textFieldBackgroundColor,
- this.outgoingChatBubbleColor,
- this.inComingChatBubbleColor,
- this.inComingChatBubbleTextColor,
- this.repliedMessageColor,
- this.closeIconColor,
- this.shareIconBackgroundColor,
- this.sendButtonColor,
- this.replyMessageColor,
- this.appBarTitleTextStyle,
- this.messageReactionBackGroundColor,
- this.messageReactionBorderColor,
- this.verticalBarColor,
- this.chatHeaderColor,
- this.themeIconColor,
- this.shareIconColor,
- this.elevation,
- this.messageTimeIconColor,
- this.messageTimeTextColor,
- this.replyPopupColor,
- this.recordIconColor,
- this.stopIconColor,
- this.waveformBackgroundColor,
- this.waveColor,
- this.replyMicIconColor,
- });
-}
-
-class DarkTheme extends AppTheme {
- DarkTheme({
- Color flashingCircleDarkColor = Colors.grey,
- Color flashingCircleBrightColor = const Color(0xffeeeeee),
- TextStyle incomingChatLinkTitleStyle = const TextStyle(color: Colors.black),
- TextStyle outgoingChatLinkTitleStyle = const TextStyle(color: Colors.white),
- TextStyle outgoingChatLinkBodyStyle = const TextStyle(color: Colors.white),
- TextStyle incomingChatLinkBodyStyle = const TextStyle(color: Colors.white),
- double elevation = 1,
- Color repliedTitleTextColor = Colors.white,
- Color? swipeToReplyIconColor = Colors.white,
- Color textFieldTextColor = Colors.white,
- Color appBarColor = const Color(0xff1d1b25),
- Color backArrowColor = Colors.white,
- Color backgroundColor = const Color(0xff272336),
- Color replyDialogColor = const Color(0xff272336),
- Color linkPreviewOutgoingChatColor = const Color(0xff272336),
- Color linkPreviewIncomingChatColor = const Color(0xff9f85ff),
- TextStyle linkPreviewIncomingTitleStyle = const TextStyle(),
- TextStyle linkPreviewOutgoingTitleStyle = const TextStyle(),
- Color replyTitleColor = Colors.white,
- Color textFieldBackgroundColor = const Color(0xff383152),
- Color outgoingChatBubbleColor = const Color(0xff9f85ff),
- Color inComingChatBubbleColor = const Color(0xff383152),
- Color reactionPopupColor = const Color(0xff383152),
- Color replyPopupColor = const Color(0xff383152),
- Color replyPopupButtonColor = Colors.white,
- Color replyPopupTopBorderColor = Colors.black54,
- Color reactionPopupTitleColor = Colors.white,
- Color inComingChatBubbleTextColor = Colors.white,
- Color repliedMessageColor = const Color(0xff9f85ff),
- Color closeIconColor = Colors.white,
- Color shareIconBackgroundColor = const Color(0xff383152),
- Color sendButtonColor = Colors.white,
- Color cameraIconColor = const Color(0xff757575),
- Color galleryIconColor = const Color(0xff757575),
- Color recorderIconColor = const Color(0xff757575),
- Color stopIconColor = const Color(0xff757575),
- Color replyMessageColor = Colors.grey,
- Color appBarTitleTextStyle = Colors.white,
- Color messageReactionBackGroundColor = const Color(0xff383152),
- Color messageReactionBorderColor = const Color(0xff272336),
- Color verticalBarColor = const Color(0xff383152),
- Color chatHeaderColor = Colors.white,
- Color themeIconColor = Colors.white,
- Color shareIconColor = Colors.white,
- Color messageTimeIconColor = Colors.white,
- Color messageTimeTextColor = Colors.white,
- Color waveformBackgroundColor = const Color(0xff383152),
- Color waveColor = Colors.white,
- Color replyMicIconColor = Colors.white,
- }) : super(
- closeIconColor: closeIconColor,
- verticalBarColor: verticalBarColor,
- textFieldBackgroundColor: textFieldBackgroundColor,
- replyTitleColor: replyTitleColor,
- replyDialogColor: replyDialogColor,
- backgroundColor: backgroundColor,
- appBarColor: appBarColor,
- appBarTitleTextStyle: appBarTitleTextStyle,
- backArrowColor: backArrowColor,
- chatHeaderColor: chatHeaderColor,
- inComingChatBubbleColor: inComingChatBubbleColor,
- inComingChatBubbleTextColor: inComingChatBubbleTextColor,
- messageReactionBackGroundColor: messageReactionBackGroundColor,
- messageReactionBorderColor: messageReactionBorderColor,
- outgoingChatBubbleColor: outgoingChatBubbleColor,
- repliedMessageColor: repliedMessageColor,
- replyMessageColor: replyMessageColor,
- sendButtonColor: sendButtonColor,
- shareIconBackgroundColor: shareIconBackgroundColor,
- themeIconColor: themeIconColor,
- shareIconColor: shareIconColor,
- elevation: elevation,
- messageTimeIconColor: messageTimeIconColor,
- messageTimeTextColor: messageTimeTextColor,
- textFieldTextColor: textFieldTextColor,
- repliedTitleTextColor: repliedTitleTextColor,
- swipeToReplyIconColor: swipeToReplyIconColor,
- reactionPopupColor: reactionPopupColor,
- replyPopupColor: replyPopupColor,
- replyPopupButtonColor: replyPopupButtonColor,
- replyPopupTopBorderColor: replyPopupTopBorderColor,
- reactionPopupTitleColor: reactionPopupTitleColor,
- linkPreviewOutgoingChatColor: linkPreviewOutgoingChatColor,
- linkPreviewIncomingChatColor: linkPreviewIncomingChatColor,
- linkPreviewIncomingTitleStyle: linkPreviewIncomingTitleStyle,
- linkPreviewOutgoingTitleStyle: linkPreviewOutgoingTitleStyle,
- incomingChatLinkBodyStyle: incomingChatLinkBodyStyle,
- incomingChatLinkTitleStyle: incomingChatLinkTitleStyle,
- outgoingChatLinkBodyStyle: outgoingChatLinkBodyStyle,
- outgoingChatLinkTitleStyle: outgoingChatLinkTitleStyle,
- flashingCircleDarkColor: flashingCircleDarkColor,
- flashingCircleBrightColor: flashingCircleBrightColor,
- galleryIconColor: galleryIconColor,
- cameraIconColor: cameraIconColor,
- recordIconColor: recorderIconColor,
- stopIconColor: stopIconColor,
- waveformBackgroundColor: waveformBackgroundColor,
- waveColor: waveColor,
- replyMicIconColor: replyMicIconColor,
- );
-}
-
-class LightTheme extends AppTheme {
- LightTheme({
- Color flashingCircleDarkColor = const Color(0xffEE5366),
- Color flashingCircleBrightColor = const Color(0xffFCD8DC),
- TextStyle incomingChatLinkTitleStyle = const TextStyle(color: Colors.black),
- TextStyle outgoingChatLinkTitleStyle = const TextStyle(color: Colors.black),
- TextStyle outgoingChatLinkBodyStyle = const TextStyle(color: Colors.grey),
- TextStyle incomingChatLinkBodyStyle = const TextStyle(color: Colors.grey),
- Color textFieldTextColor = Colors.black,
- Color repliedTitleTextColor = Colors.black,
- Color swipeToReplyIconColor = Colors.black,
- double elevation = 2,
- Color appBarColor = Colors.white,
- Color backArrowColor = const Color(0xffEE5366),
- Color backgroundColor = const Color(0xffeeeeee),
- Color replyDialogColor = const Color(0xffFCD8DC),
- Color linkPreviewOutgoingChatColor = const Color(0xffFCD8DC),
- Color linkPreviewIncomingChatColor = const Color(0xFFEEEEEE),
- TextStyle linkPreviewIncomingTitleStyle = const TextStyle(),
- TextStyle linkPreviewOutgoingTitleStyle = const TextStyle(),
- Color replyTitleColor = const Color(0xffEE5366),
- Color reactionPopupColor = Colors.white,
- Color replyPopupColor = Colors.white,
- Color replyPopupButtonColor = Colors.black,
- Color replyPopupTopBorderColor = const Color(0xFFBDBDBD),
- Color reactionPopupTitleColor = Colors.grey,
- Color textFieldBackgroundColor = Colors.white,
- Color outgoingChatBubbleColor = const Color(0xffEE5366),
- Color inComingChatBubbleColor = Colors.white,
- Color inComingChatBubbleTextColor = Colors.black,
- Color repliedMessageColor = const Color(0xffff8aad),
- Color closeIconColor = Colors.black,
- Color shareIconBackgroundColor = const Color(0xFFE0E0E0),
- Color sendButtonColor = const Color(0xffEE5366),
- Color cameraIconColor = Colors.black,
- Color galleryIconColor = Colors.black,
- Color replyMessageColor = Colors.black,
- Color appBarTitleTextStyle = Colors.black,
- Color messageReactionBackGroundColor = const Color(0xFFEEEEEE),
- Color messageReactionBorderColor = Colors.white,
- Color verticalBarColor = const Color(0xffEE5366),
- Color chatHeaderColor = Colors.black,
- Color themeIconColor = Colors.black,
- Color shareIconColor = Colors.black,
- Color messageTimeIconColor = Colors.black,
- Color messageTimeTextColor = Colors.black,
- Color recorderIconColor = Colors.black,
- Color stopIconColor = Colors.black,
- Color waveformBackgroundColor = Colors.white,
- Color waveColor = Colors.black,
- Color replyMicIconColor = Colors.black,
- }) : super(
- reactionPopupColor: reactionPopupColor,
- closeIconColor: closeIconColor,
- verticalBarColor: verticalBarColor,
- textFieldBackgroundColor: textFieldBackgroundColor,
- replyTitleColor: replyTitleColor,
- replyDialogColor: replyDialogColor,
- backgroundColor: backgroundColor,
- appBarColor: appBarColor,
- appBarTitleTextStyle: appBarTitleTextStyle,
- backArrowColor: backArrowColor,
- chatHeaderColor: chatHeaderColor,
- inComingChatBubbleColor: inComingChatBubbleColor,
- inComingChatBubbleTextColor: inComingChatBubbleTextColor,
- messageReactionBackGroundColor: messageReactionBackGroundColor,
- messageReactionBorderColor: messageReactionBorderColor,
- outgoingChatBubbleColor: outgoingChatBubbleColor,
- repliedMessageColor: repliedMessageColor,
- replyMessageColor: replyMessageColor,
- sendButtonColor: sendButtonColor,
- shareIconBackgroundColor: shareIconBackgroundColor,
- themeIconColor: themeIconColor,
- shareIconColor: shareIconColor,
- elevation: elevation,
- messageTimeIconColor: messageTimeIconColor,
- messageTimeTextColor: messageTimeTextColor,
- textFieldTextColor: textFieldTextColor,
- repliedTitleTextColor: repliedTitleTextColor,
- swipeToReplyIconColor: swipeToReplyIconColor,
- replyPopupColor: replyPopupColor,
- replyPopupButtonColor: replyPopupButtonColor,
- replyPopupTopBorderColor: replyPopupTopBorderColor,
- reactionPopupTitleColor: reactionPopupTitleColor,
- linkPreviewOutgoingChatColor: linkPreviewOutgoingChatColor,
- linkPreviewIncomingChatColor: linkPreviewIncomingChatColor,
- linkPreviewIncomingTitleStyle: linkPreviewIncomingTitleStyle,
- linkPreviewOutgoingTitleStyle: linkPreviewOutgoingTitleStyle,
- incomingChatLinkBodyStyle: incomingChatLinkBodyStyle,
- incomingChatLinkTitleStyle: incomingChatLinkTitleStyle,
- outgoingChatLinkBodyStyle: outgoingChatLinkBodyStyle,
- outgoingChatLinkTitleStyle: outgoingChatLinkTitleStyle,
- flashingCircleDarkColor: flashingCircleDarkColor,
- flashingCircleBrightColor: flashingCircleBrightColor,
- galleryIconColor: galleryIconColor,
- cameraIconColor: cameraIconColor,
- stopIconColor: stopIconColor,
- recordIconColor: recorderIconColor,
- waveformBackgroundColor: waveformBackgroundColor,
- waveColor: waveColor,
- replyMicIconColor: replyMicIconColor,
- );
-}
diff --git a/example/lib/values/colors.dart b/example/lib/values/colors.dart
new file mode 100644
index 00000000..fc88ea58
--- /dev/null
+++ b/example/lib/values/colors.dart
@@ -0,0 +1,16 @@
+import 'dart:ui';
+
+class AppColors {
+ // ChatView Colors
+ static const Color grey200 = Color(0xffF6F5F4);
+ static const Color red200 = Color(0xFFFFCDD2);
+ static const Color black = Color(0xff000000);
+ static const Color black20 = Color(0x33000000);
+ static const Color white = Color(0xffffffff);
+ static const Color white20 = Color(0x33ffffff);
+
+ // Instagram Colors
+ static const Color instaDarkGrey = Color(0xff767779);
+ static const Color instaPurple = Color(0xFF574FF0);
+ static const Color instaUnreadCountDot = Color(0xff5C6BF6);
+}
diff --git a/example/lib/values/icons.dart b/example/lib/values/icons.dart
new file mode 100644
index 00000000..ccf0e5da
--- /dev/null
+++ b/example/lib/values/icons.dart
@@ -0,0 +1,11 @@
+class AppIcons {
+ static const String camera2 = 'assets/vectors/camera_2.svg';
+ static const String chatDiscoveryAi = 'assets/vectors/chat_discovery_ai.svg';
+ static const String createPen = 'assets/vectors/create_pen.svg';
+ static const String checkMark = "assets/vectors/check_mark.svg";
+ static const String pinned = "assets/vectors/pinned.svg";
+ static const String video = "assets/vectors/video.svg";
+ static const String phone = "assets/vectors/phone.svg";
+ static const String sticker = "assets/vectors/sticker.svg";
+ static const String ai = "assets/vectors/ai_logo.svg";
+}
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index 8a7f3b9b..4c15f737 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -12,8 +12,11 @@ dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.5
+ flutter_svg: ^2.2.1
+ audio_waveforms: ^1.3.0
chatview:
path: ../
+ intl:
dev_dependencies:
flutter_test:
@@ -24,3 +27,4 @@ flutter:
uses-material-design: true
assets:
- assets/images/
+ - assets/vectors/
diff --git a/lib/src/models/config_models/chat_view_list/list_tile_config.dart b/lib/src/models/config_models/chat_view_list/list_tile_config.dart
index 691a7a83..f3f4564e 100644
--- a/lib/src/models/config_models/chat_view_list/list_tile_config.dart
+++ b/lib/src/models/config_models/chat_view_list/list_tile_config.dart
@@ -52,8 +52,12 @@ class ListTileConfig {
this.middleWidgetPadding = const EdgeInsets.symmetric(horizontal: 12),
this.padding = const EdgeInsets.symmetric(vertical: 6, horizontal: 8),
this.lastMessageStatusConfig = const LastMessageStatusConfig(),
+ this.lastMessageTextStyle = const TextStyle(
+ fontSize: 14,
+ fontWeight: FontWeight.normal,
+ ),
+ this.highlightTextStyle = const TextStyle(fontWeight: FontWeight.bold),
this.userNameTextStyle,
- this.lastMessageTextStyle,
this.onTap,
this.lastMessageTileBuilder,
this.userNameBuilder,
@@ -69,6 +73,9 @@ class ListTileConfig {
final UserNameBuilder? userNameBuilder;
/// Custom widget builder for the trailing widget in the chat list.
+ ///
+ /// Defaults it will show last message createAtTime, unread count,
+ /// mute icon and pin icon.
final TrailingBuilder? trailingBuilder;
/// Text styles for various text elements in the user widget.
@@ -87,6 +94,14 @@ class ListTileConfig {
/// Text styles for the last message text in the user widget.
final TextStyle? lastMessageTextStyle;
+ /// Text style for highlighting the last message text,
+ /// typically used for unread messages.
+ ///
+ /// Defaults to a bold font weight.
+ ///
+ /// If null, no special highlighting is applied.
+ final TextStyle? highlightTextStyle;
+
/// Text overflow behavior for the last message text.
///
/// Defaults to `TextOverflow.ellipsis`.
diff --git a/lib/src/models/config_models/chat_view_list/search_config.dart b/lib/src/models/config_models/chat_view_list/search_config.dart
index cc70c03a..b141ba0c 100644
--- a/lib/src/models/config_models/chat_view_list/search_config.dart
+++ b/lib/src/models/config_models/chat_view_list/search_config.dart
@@ -40,6 +40,7 @@ class SearchConfig {
this.borderRadius = const BorderRadius.all(
Radius.circular(textFieldBorderRadius),
),
+ this.clearIcon = const Icon(Icons.clear),
this.onTapOutside,
this.textStyle,
this.maxLines,
@@ -132,6 +133,13 @@ class SearchConfig {
/// Decoration for the search text field.
final InputDecoration? decoration;
+ /// Icon to clear the search text field.
+ ///
+ /// Defaults to an clear icon `Icons.clear`.
+ ///
+ /// **Note**: if suffixIcon is provided, this will be ignored.
+ final Widget clearIcon;
+
/// Callback function that is called when the user taps outside the search
/// text field.
final TapRegionCallback? onTapOutside;
diff --git a/lib/src/models/config_models/replied_message_configuration.dart b/lib/src/models/config_models/replied_message_configuration.dart
index 8b660fbc..d824384e 100644
--- a/lib/src/models/config_models/replied_message_configuration.dart
+++ b/lib/src/models/config_models/replied_message_configuration.dart
@@ -44,6 +44,8 @@ class RepliedMessageConfiguration {
this.micIconColor,
});
+ /// Used to fetch old replied message when original message is not present
+ /// in the current list of messages.
final OldReplyMessageFetchCallback loadOldReplyMessage;
/// Used to give color to vertical bar.
diff --git a/lib/src/widgets/chat_view_list/chat_view_list_item_tile.dart b/lib/src/widgets/chat_view_list/chat_view_list_item_tile.dart
index 214f8e52..38a9a748 100644
--- a/lib/src/widgets/chat_view_list/chat_view_list_item_tile.dart
+++ b/lib/src/widgets/chat_view_list/chat_view_list_item_tile.dart
@@ -146,6 +146,7 @@ class ChatViewListItemTile extends StatelessWidget {
config.lastMessageTextOverflow,
lastMessageTextStyle:
config.lastMessageTextStyle,
+ highlightTextStyle: config.highlightTextStyle,
lastMessageBuilder:
config.lastMessageTileBuilder?.call(chat),
statusConfig: config.lastMessageStatusConfig,
diff --git a/lib/src/widgets/chat_view_list/last_message_view.dart b/lib/src/widgets/chat_view_list/last_message_view.dart
index 0fc57c94..88ce648f 100644
--- a/lib/src/widgets/chat_view_list/last_message_view.dart
+++ b/lib/src/widgets/chat_view_list/last_message_view.dart
@@ -11,6 +11,7 @@ class LastMessageView extends StatelessWidget {
required this.lastMessage,
required this.showStatusIcon,
required this.statusConfig,
+ this.highlightTextStyle,
this.lastMessageType,
this.lastMessageBuilder,
this.lastMessageMaxLines,
@@ -36,11 +37,12 @@ class LastMessageView extends StatelessWidget {
final MessageType? lastMessageType;
final Widget? lastMessageBuilder;
final bool showStatusIcon;
+ final TextStyle? highlightTextStyle;
final LastMessageStatusConfig statusConfig;
@override
Widget build(BuildContext context) {
- final highlightText = unreadCount > 0;
+ final highlightText = highlightTextStyle != null && unreadCount > 0;
return lastMessageBuilder ??
AnimatedSwitcher(
switchOutCurve: Curves.easeOut,
@@ -71,10 +73,8 @@ class LastMessageView extends StatelessWidget {
PackageStrings.currentLocale.photo,
maxLines: lastMessageMaxLines,
overflow: lastMessageTextOverflow,
- style: TextStyle(
- fontWeight: highlightText
- ? FontWeight.bold
- : FontWeight.normal,
+ style: lastMessageTextStyle?.merge(
+ highlightText ? highlightTextStyle : null,
),
),
),
@@ -85,13 +85,9 @@ class LastMessageView extends StatelessWidget {
textAlign: TextAlign.left,
maxLines: lastMessageMaxLines,
overflow: lastMessageTextOverflow,
- style: lastMessageTextStyle ??
- TextStyle(
- fontSize: 14,
- fontWeight: highlightText
- ? FontWeight.bold
- : FontWeight.normal,
- ),
+ style: lastMessageTextStyle?.merge(
+ highlightText ? highlightTextStyle : null,
+ ),
),
MessageType.voice => Row(
mainAxisSize: MainAxisSize.min,
@@ -103,6 +99,9 @@ class LastMessageView extends StatelessWidget {
PackageStrings.currentLocale.voice,
maxLines: lastMessageMaxLines,
overflow: lastMessageTextOverflow,
+ style: lastMessageTextStyle?.merge(
+ highlightText ? highlightTextStyle : null,
+ ),
),
),
],
diff --git a/lib/src/widgets/chat_view_list/search_text_field.dart b/lib/src/widgets/chat_view_list/search_text_field.dart
index 719410b6..c9ddf60e 100644
--- a/lib/src/widgets/chat_view_list/search_text_field.dart
+++ b/lib/src/widgets/chat_view_list/search_text_field.dart
@@ -93,7 +93,7 @@ class _SearchTextFieldState extends State {
builder: (context, value, _) => value.isEmpty
? const SizedBox.shrink()
: IconButton(
- icon: const Icon(Icons.clear),
+ icon: _config.clearIcon,
onPressed: _onTapClear,
),
),
diff --git a/lib/src/widgets/chatui_textfield.dart b/lib/src/widgets/chatui_textfield.dart
index 8eeef49c..7778663c 100644
--- a/lib/src/widgets/chatui_textfield.dart
+++ b/lib/src/widgets/chatui_textfield.dart
@@ -108,6 +108,9 @@ class _ChatUITextFieldState extends State {
@override
void initState() {
attachListeners();
+ // Used to notify instead of onChanged of text field because
+ // onChanged is not called when text is set programmatically.
+ widget.textEditingController.addListener(_listenTextEditingController);
debouncer = Debouncer(
sendMessageConfig?.textFieldConfig?.compositionThresholdTime ??
const Duration(seconds: 1));
@@ -133,6 +136,7 @@ class _ChatUITextFieldState extends State {
if (_keyboardHandler case final handler?) {
HardwareKeyboard.instance.removeHandler(handler);
}
+ widget.textEditingController.removeListener(_listenTextEditingController);
super.dispose();
}
@@ -259,7 +263,6 @@ class _ChatUITextFieldState extends State {
minLines: textFieldConfig?.minLines ?? 1,
keyboardType: textFieldConfig?.textInputType,
inputFormatters: textFieldConfig?.inputFormatters,
- onChanged: _onChanged,
enabled: textFieldConfig?.enabled,
textCapitalization:
textFieldConfig?.textCapitalization ??
@@ -439,6 +442,12 @@ class _ChatUITextFieldState extends State {
}
}
+ void _listenTextEditingController() {
+ widget.textEditingController.addListener(() {
+ _onChanged(widget.textEditingController.text);
+ });
+ }
+
void _onChanged(String inputText) {
debouncer.run(onComplete: () {
composingStatus.value = TypeWriterStatus.typed;
diff --git a/lib/src/widgets/type_indicator/type_indicator_widget.dart b/lib/src/widgets/type_indicator/type_indicator_widget.dart
index e3816d30..813c99e2 100644
--- a/lib/src/widgets/type_indicator/type_indicator_widget.dart
+++ b/lib/src/widgets/type_indicator/type_indicator_widget.dart
@@ -197,6 +197,7 @@ class _TypingIndicatorState extends State
child: Padding(
padding: typeIndicatorPadding,
child: UserTypingBuilder(
+ showProfileCircle: false,
animation: _largeBubbleAnimation,
profileConfig: profileCircleConfiguration,
bubble: widget.typeIndicatorConfig.customIndicator!,
diff --git a/lib/src/widgets/type_indicator/user_typing_builder.dart b/lib/src/widgets/type_indicator/user_typing_builder.dart
index 56d6ed28..7570dbe5 100644
--- a/lib/src/widgets/type_indicator/user_typing_builder.dart
+++ b/lib/src/widgets/type_indicator/user_typing_builder.dart
@@ -8,6 +8,7 @@ class UserTypingBuilder extends StatelessWidget {
const UserTypingBuilder({
required this.animation,
required this.bubble,
+ this.showProfileCircle = true,
this.profileConfig,
super.key,
});
@@ -15,6 +16,7 @@ class UserTypingBuilder extends StatelessWidget {
final Widget bubble;
final Animation animation;
final ProfileCircleConfiguration? profileConfig;
+ final bool showProfileCircle;
@override
Widget build(BuildContext context) {
@@ -25,24 +27,27 @@ class UserTypingBuilder extends StatelessWidget {
alignment: Alignment.centerLeft,
child: child,
),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- ProfileCircle(
- bottomPadding: 0,
- profileCirclePadding: EdgeInsets.zero,
- imageUrl: profileConfig?.profileImageUrl,
- imageType: profileConfig?.imageType,
- assetImageErrorBuilder: profileConfig?.assetImageErrorBuilder,
- networkImageErrorBuilder: profileConfig?.networkImageErrorBuilder,
- defaultAvatarImage:
- profileConfig?.defaultAvatarImage ?? Constants.profileImage,
- networkImageProgressIndicatorBuilder:
- profileConfig?.networkImageProgressIndicatorBuilder,
- ),
- bubble,
- ],
- ),
+ child: !showProfileCircle
+ ? bubble
+ : Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ ProfileCircle(
+ bottomPadding: 0,
+ profileCirclePadding: EdgeInsets.zero,
+ imageUrl: profileConfig?.profileImageUrl,
+ imageType: profileConfig?.imageType,
+ assetImageErrorBuilder: profileConfig?.assetImageErrorBuilder,
+ networkImageErrorBuilder:
+ profileConfig?.networkImageErrorBuilder,
+ defaultAvatarImage: profileConfig?.defaultAvatarImage ??
+ Constants.profileImage,
+ networkImageProgressIndicatorBuilder:
+ profileConfig?.networkImageProgressIndicatorBuilder,
+ ),
+ bubble,
+ ],
+ ),
);
}
}
diff --git a/preview/chatviewlist.gif b/preview/chatviewlist.gif
new file mode 100644
index 00000000..7d1b3fa5
Binary files /dev/null and b/preview/chatviewlist.gif differ
diff --git a/pubspec.yaml b/pubspec.yaml
index 523c36a2..631d2af7 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -23,7 +23,7 @@ dependencies:
chatview_utils:
git:
url: https://github.com/SimformSolutionsPvtLtd/chatview_utils
- ref: feature/add_chatview_list_models_test
+ ref: feature/update_changelog
emoji_picker_flutter: ^4.3.0
flutter: