diff --git a/example/assets/images/chat_background.png b/example/assets/images/chat_background.png
new file mode 100644
index 00000000..cb17a0ff
Binary files /dev/null and b/example/assets/images/chat_background.png differ
diff --git a/example/assets/vectors/add.svg b/example/assets/vectors/add.svg
new file mode 100644
index 00000000..960a62d3
--- /dev/null
+++ b/example/assets/vectors/add.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/assets/vectors/archived.svg b/example/assets/vectors/archived.svg
new file mode 100644
index 00000000..a1b177c0
--- /dev/null
+++ b/example/assets/vectors/archived.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/assets/vectors/calls.svg b/example/assets/vectors/calls.svg
new file mode 100644
index 00000000..2b9048ea
--- /dev/null
+++ b/example/assets/vectors/calls.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/assets/vectors/camera.svg b/example/assets/vectors/camera.svg
new file mode 100644
index 00000000..2e881fa0
--- /dev/null
+++ b/example/assets/vectors/camera.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/assets/vectors/camera_outline.svg b/example/assets/vectors/camera_outline.svg
new file mode 100644
index 00000000..93c343e3
--- /dev/null
+++ b/example/assets/vectors/camera_outline.svg
@@ -0,0 +1,3 @@
+
diff --git a/example/assets/vectors/close_circular.svg b/example/assets/vectors/close_circular.svg
new file mode 100644
index 00000000..9702189d
--- /dev/null
+++ b/example/assets/vectors/close_circular.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/assets/vectors/community.svg b/example/assets/vectors/community.svg
new file mode 100644
index 00000000..e3c26dad
--- /dev/null
+++ b/example/assets/vectors/community.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/assets/vectors/lock.svg b/example/assets/vectors/lock.svg
new file mode 100644
index 00000000..0b98e414
--- /dev/null
+++ b/example/assets/vectors/lock.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/assets/vectors/messages.svg b/example/assets/vectors/messages.svg
new file mode 100644
index 00000000..b45781c6
--- /dev/null
+++ b/example/assets/vectors/messages.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/assets/vectors/mic.svg b/example/assets/vectors/mic.svg
new file mode 100644
index 00000000..7eb2ba36
--- /dev/null
+++ b/example/assets/vectors/mic.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/assets/vectors/more_horizontal.svg b/example/assets/vectors/more_horizontal.svg
new file mode 100644
index 00000000..a9b0dc9b
--- /dev/null
+++ b/example/assets/vectors/more_horizontal.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/assets/vectors/plus.svg b/example/assets/vectors/plus.svg
new file mode 100644
index 00000000..92fb8c39
--- /dev/null
+++ b/example/assets/vectors/plus.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/assets/vectors/send.svg b/example/assets/vectors/send.svg
new file mode 100644
index 00000000..36adbf96
--- /dev/null
+++ b/example/assets/vectors/send.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/assets/vectors/settings.svg b/example/assets/vectors/settings.svg
new file mode 100644
index 00000000..b7b1a5e6
--- /dev/null
+++ b/example/assets/vectors/settings.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/assets/vectors/status.svg b/example/assets/vectors/status.svg
new file mode 100644
index 00000000..88916074
--- /dev/null
+++ b/example/assets/vectors/status.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/example/lib/data.dart b/example/lib/data.dart
index 93dc04c6..9d3697a1 100644
--- a/example/lib/data.dart
+++ b/example/lib/data.dart
@@ -317,21 +317,18 @@ class Data {
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,
+ id: '19',
+ message: 'https://bit.ly/3JHS2Wl',
+ createdAt: DateTime.now().subtract(const Duration(minutes: 3)),
+ sentBy: '2',
status: MessageStatus.read,
+ messageType: MessageType.text,
),
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)),
+ id: '20',
+ message: "Check this out! We can meet here.",
+ createdAt: DateTime.now().subtract(const Duration(minutes: 1)),
sentBy: '2',
- messageType: MessageType.voice,
status: MessageStatus.read,
),
];
diff --git a/example/lib/example_two/example_two_chat_screen.dart b/example/lib/example_two/example_two_chat_screen.dart
new file mode 100644
index 00000000..621add52
--- /dev/null
+++ b/example/lib/example_two/example_two_chat_screen.dart
@@ -0,0 +1,485 @@
+import 'package:chatview/chatview.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:intl/intl.dart';
+
+import '../data.dart';
+import '../values/colors.dart';
+import '../values/borders.dart';
+import '../values/icons.dart';
+import '../values/images.dart';
+import '../widgets/reply_message_tile.dart';
+import '../widgets/wp_custom_chat_bar.dart';
+
+class ExampleTwoChatScreen extends StatefulWidget {
+ const ExampleTwoChatScreen({required this.chat, super.key});
+
+ final ChatViewListItem chat;
+
+ @override
+ State createState() => _ExampleTwoChatScreenState();
+}
+
+class _ExampleTwoChatScreenState extends State {
+ bool _isTopPaginationCalled = false;
+ bool _isBottomPaginationCalled = false;
+
+ final ChatController _chatController = ChatController(
+ initialMessageList: Data.getMessageList(isExampleOne: false),
+ scrollController: ScrollController(),
+ currentUser: Data.currentUser,
+ otherUsers: Data.otherUsers,
+ );
+
+ @override
+ void initState() {
+ super.initState();
+
+ // Set system UI overlay style
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ SystemChrome.setSystemUIOverlayStyle(
+ const SystemUiOverlayStyle(
+ statusBarColor: Color(0xFFE5DDD5),
+ statusBarBrightness: Brightness.dark,
+ ),
+ );
+ });
+ }
+
+ @override
+ void dispose() {
+ _chatController.dispose();
+ super.dispose();
+ }
+
+ void _showHideTypingIndicator() {
+ _chatController.setTypingIndicator = !_chatController.showTypingIndicator;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ backgroundColor: AppColors.uiTwoBackground,
+ body: ChatView(
+ chatController: _chatController,
+ chatViewState: ChatViewState.hasMessages,
+
+ 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,
+ );
+ },
+ appBar: ChatViewAppBar(
+ elevation: 0,
+ chatTitle: widget.chat.name,
+ chatTitleTextStyle: const TextStyle(
+ fontSize: 18,
+ color: Colors.black,
+ fontWeight: FontWeight.w600,
+ ),
+ profilePicture: widget.chat.imageUrl,
+ backGroundColor: AppColors.uiTwoBackground,
+ userStatus: 'tap here for contact info',
+ userStatusTextStyle: const TextStyle(
+ fontSize: 13,
+ color: Colors.black87,
+ ),
+ actions: [
+ IconButton(
+ // Handle video call
+ onPressed: () => showSnackBar('Video call pressed'),
+ icon: SvgPicture.asset(AppIcons.video),
+ ),
+ IconButton(
+ // Handle voice call
+ onPressed: () => showSnackBar('Voice call pressed'),
+ icon: SvgPicture.asset(AppIcons.phone),
+ ),
+ PopupMenuButton(
+ icon: const Icon(Icons.more_vert_rounded),
+ itemBuilder: (context) => [
+ const PopupMenuItem(
+ value: 'toggle_typing_indicator',
+ child: Text('Toggle TypingIndicator'),
+ ),
+ ],
+ onSelected: (value) {
+ switch (value) {
+ case 'toggle_typing_indicator':
+ _showHideTypingIndicator();
+ }
+ },
+ ),
+ const SizedBox(width: 12),
+ ],
+ ),
+ typeIndicatorConfig: TypeIndicatorConfiguration(
+ customIndicator: _customTypingIndicator(),
+ ),
+ profileCircleConfig: const ProfileCircleConfiguration(
+ padding: EdgeInsets.only(right: 4),
+ ),
+ chatBackgroundConfig: ChatBackgroundConfiguration(
+ backgroundColor: AppColors.uiTwoBackground,
+ backgroundImage: AppImages.wpChatBackground,
+ groupSeparatorBuilder: (separator) => _customSeparator(separator),
+ ),
+ chatBubbleConfig: ChatBubbleConfiguration(
+ outgoingChatBubbleConfig: const ChatBubble(
+ linkPreviewConfig: LinkPreviewConfiguration(
+ linkStyle: TextStyle(color: Colors.black87, fontSize: 16),
+ ),
+ border: AppBorders.exampleTwoMessageBorder,
+ color: Color(0xFFD0FECF),
+ borderRadius: BorderRadius.only(
+ topLeft: Radius.circular(12),
+ topRight: Radius.circular(12),
+ bottomLeft: Radius.circular(12),
+ bottomRight: Radius.circular(4),
+ ),
+ textStyle: TextStyle(color: Colors.black87, fontSize: 16),
+ padding: EdgeInsets.all(5.5),
+ receiptsWidgetConfig: ReceiptsWidgetConfig(
+ showReceiptsIn: ShowReceiptsIn.lastMessage,
+ ),
+ ),
+ inComingChatBubbleConfig: ChatBubble(
+ linkPreviewConfig: const LinkPreviewConfiguration(
+ linkStyle: TextStyle(color: Colors.black87, fontSize: 16),
+ ),
+ border: AppBorders.exampleTwoMessageBorder,
+ color: Colors.white,
+ borderRadius: const BorderRadius.only(
+ topLeft: Radius.circular(12),
+ topRight: Radius.circular(12),
+ bottomLeft: Radius.circular(4),
+ bottomRight: Radius.circular(12),
+ ),
+ textStyle: const TextStyle(color: Colors.black87, fontSize: 16),
+ padding: const EdgeInsets.all(5.5),
+ onMessageRead: (message) {
+ /// send your message reciepts to the other client
+ debugPrint('Message Read');
+ },
+ ),
+ ),
+ messageConfig: MessageConfiguration(
+ voiceMessageConfig: VoiceMessageConfiguration(
+ margin: EdgeInsets.zero,
+ padding: EdgeInsets.zero,
+ playIcon: (_) => const Icon(
+ Icons.play_arrow_rounded,
+ size: 38,
+ color: Color(0xff767779),
+ ),
+ pauseIcon: (_) => const Icon(
+ Icons.pause_rounded,
+ size: 38,
+ color: Color(0xff767779),
+ ),
+ inComingPlayerWaveStyle: const PlayerWaveStyle(
+ liveWaveColor: Color(0xff000000),
+ fixedWaveColor: Color(0x33000000),
+ backgroundColor: Colors.transparent,
+ scaleFactor: 60,
+ waveThickness: 3,
+ spacing: 4,
+ ),
+ outgoingPlayerWaveStyle: const PlayerWaveStyle(
+ liveWaveColor: Color(0xff000000),
+ fixedWaveColor: Color(0x33000000),
+ backgroundColor: Colors.transparent,
+ scaleFactor: 60,
+ waveThickness: 3,
+ spacing: 4,
+ ),
+ ),
+ messageReactionConfig: MessageReactionConfiguration(
+ backgroundColor: Colors.white,
+ borderColor: Colors.grey.shade300,
+ reactionsBottomSheetConfig: const ReactionsBottomSheetConfiguration(
+ backgroundColor: Colors.white,
+ ),
+ ),
+ // Custom message builder for location messages
+ customMessageBuilder: (message) {
+ // For demo, we check if the message contains 'Twin Pines Mall'
+ if (message.message.contains('Twin Pines Mall')) {
+ return _buildLocationMessage(message);
+ }
+ return const SizedBox.shrink();
+ },
+ ),
+
+ // Reply message configuration
+ repliedMessageConfig: RepliedMessageConfiguration(
+ margin: const EdgeInsets.symmetric(horizontal: 12),
+ backgroundColor: Colors.grey.shade100,
+ verticalBarColor: const Color(0xFF128C7E),
+ loadOldReplyMessage: (messageId) async {},
+ repliedMessageWidgetBuilder: (replyMessage) => ReplyMessageTile(
+ replyMessage: replyMessage,
+ chatController: _chatController,
+ ),
+ ),
+
+ // Swipe to reply configuration
+ swipeToReplyConfig: const SwipeToReplyConfiguration(
+ onLeftSwipe: null,
+ onRightSwipe: null,
+ ),
+ sendMessageBuilder: (replyMessage) => WpCustomChatBar(
+ chatController: _chatController,
+ replyMessage: replyMessage ?? const ReplyMessage(),
+ onAttachPressed: () => ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(content: Text('Attach button pressed')),
+ ),
+ ),
+ featureActiveConfig: const FeatureActiveConfig(
+ lastSeenAgoBuilderVisibility: false,
+ enableOtherUserProfileAvatar: false,
+ enableOtherUserName: false,
+ enablePagination: true,
+ ),
+ reactionPopupConfig: const ReactionPopupConfiguration(
+ backgroundColor: Colors.white,
+ shadow: BoxShadow(
+ blurRadius: 8,
+ color: Colors.black26,
+ offset: Offset(0, 4),
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _customTypingIndicator() {
+ return Container(
+ margin: const EdgeInsets.only(left: 6),
+ decoration: const BoxDecoration(
+ color: Colors.white,
+ border: AppBorders.exampleTwoMessageBorder,
+ borderRadius: BorderRadius.all(Radius.circular(12)),
+ ),
+ padding: const EdgeInsets.symmetric(
+ vertical: 12.75,
+ horizontal: 8.75,
+ ),
+ child: const Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ CircleAvatar(radius: 2.75, backgroundColor: AppColors.uiTwoGrey),
+ SizedBox(width: 3),
+ CircleAvatar(radius: 2.75, backgroundColor: AppColors.uiTwoGrey),
+ SizedBox(width: 3),
+ CircleAvatar(radius: 2.75, backgroundColor: AppColors.uiTwoGrey),
+ ],
+ ),
+ );
+ }
+
+ Widget _customSeparator(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 = 'Today';
+ } else if (date.day == now.day - 1 &&
+ date.month == now.month &&
+ date.year == now.year) {
+ separatorDate = 'Yesterday';
+ } else {
+ separatorDate = DateFormat('d MMMM y').format(date);
+ }
+ return Align(
+ child: Container(
+ margin: const EdgeInsets.symmetric(vertical: 12),
+ padding: const EdgeInsets.symmetric(
+ horizontal: 14,
+ vertical: 3,
+ ),
+ decoration: const BoxDecoration(
+ color: Colors.white,
+ border: AppBorders.exampleTwoMessageBorder,
+ borderRadius: BorderRadius.all(Radius.circular(8)),
+ ),
+ child: Text(
+ separatorDate,
+ style: const TextStyle(
+ fontSize: 12,
+ color: Color(0xff0A0A0A),
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildLocationMessage(Message message) {
+ return Container(
+ constraints: BoxConstraints(
+ maxWidth: MediaQuery.of(context).size.width * 0.75,
+ ),
+ padding: const EdgeInsets.all(12),
+ margin: EdgeInsets.fromLTRB(
+ message.sentBy == _chatController.currentUser.id ? 64 : 8,
+ 4,
+ message.sentBy == _chatController.currentUser.id ? 8 : 64,
+ 4,
+ ),
+ decoration: BoxDecoration(
+ color: message.sentBy == _chatController.currentUser.id
+ ? const Color(0xFFD0FECF)
+ : Colors.white,
+ borderRadius: const BorderRadius.all(Radius.circular(12)),
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ Icon(Icons.location_on, color: Colors.red.shade600, size: 20),
+ const SizedBox(width: 8),
+ Expanded(
+ child: Text(
+ 'Location',
+ style: TextStyle(
+ fontWeight: FontWeight.w600,
+ fontSize: 14,
+ color: Colors.grey.shade700,
+ ),
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: 8),
+ Container(
+ height: 120,
+ width: double.infinity,
+ decoration: BoxDecoration(
+ color: Colors.grey.shade300,
+ borderRadius: BorderRadius.circular(8),
+ ),
+ child: const Center(
+ child: Icon(Icons.map, size: 40, color: Colors.grey),
+ ),
+ ),
+ const SizedBox(height: 8),
+ Text(
+ message.message,
+ style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 16),
+ ),
+ Text(
+ 'Hill Valley, CA',
+ style: TextStyle(color: Colors.grey.shade600, fontSize: 14),
+ ),
+ const SizedBox(height: 4),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ Text(
+ _formatTime(message.createdAt),
+ style: TextStyle(color: Colors.grey.shade500, fontSize: 12),
+ ),
+ if (message.sentBy == _chatController.currentUser.id) ...[
+ const SizedBox(width: 4),
+ Icon(
+ _getMessageStatusIcon(message.status),
+ size: 16,
+ color: _getMessageStatusColor(message.status),
+ ),
+ ],
+ ],
+ ),
+ ],
+ ),
+ );
+ }
+
+ void showSnackBar(String message) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text(message)),
+ );
+ }
+
+ String _formatTime(DateTime dateTime) {
+ return '${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}';
+ }
+
+ IconData _getMessageStatusIcon(MessageStatus status) {
+ return switch (status) {
+ MessageStatus.delivered => Icons.done_all,
+ MessageStatus.read => Icons.done_all,
+ MessageStatus.pending => Icons.access_time,
+ MessageStatus.undelivered => Icons.error_outline,
+ };
+ }
+
+ Color _getMessageStatusColor(MessageStatus status) {
+ return switch (status) {
+ MessageStatus.read => const Color(0xFF4FC3F7),
+ MessageStatus.delivered => Colors.grey.shade600,
+ MessageStatus.pending => Colors.grey.shade500,
+ MessageStatus.undelivered => Colors.red.shade600,
+ };
+ }
+}
diff --git a/example/lib/example_two/example_two_list_screen.dart b/example/lib/example_two/example_two_list_screen.dart
new file mode 100644
index 00000000..56be51ae
--- /dev/null
+++ b/example/lib/example_two/example_two_list_screen.dart
@@ -0,0 +1,616 @@
+import 'package:chatview/chatview.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+
+import '../data.dart';
+import '../main.dart';
+import '../models/chatview_list_theme.dart';
+import '../values/colors.dart';
+import '../values/icons.dart';
+import 'example_two_chat_screen.dart';
+
+class ExampleTwoListScreen extends StatefulWidget {
+ const ExampleTwoListScreen({super.key});
+
+ @override
+ State createState() => _ExampleTwoListScreenState();
+}
+
+class _ExampleTwoListScreenState extends State {
+ ChatViewListTheme _theme = ChatViewListTheme.uiTwoLight;
+ bool _isDarkTheme = false;
+
+ final _searchController = TextEditingController();
+
+ ChatViewListController? _chatListController;
+
+ ScrollController? _scrollController;
+
+ String _selectedFilter = 'All';
+
+ @override
+ void initState() {
+ super.initState();
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ SystemChrome.setSystemUIOverlayStyle(
+ const SystemUiOverlayStyle(statusBarColor: AppColors.uiTwoBackground),
+ );
+ });
+ }
+
+ // Assign the controller in didChangeDependencies
+ // to ensure PrimaryScrollController is available.
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+ _scrollController = PrimaryScrollController.of(context);
+ _chatListController ??= ChatViewListController(
+ initialChatList: Data.getChatList(),
+ scrollController: _scrollController!,
+ disposeOtherResources: false,
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Theme(
+ data: Theme.of(context).copyWith(
+ primaryColor: AppColors.uiTwoGreen,
+ colorScheme: ColorScheme.fromSwatch(accentColor: AppColors.uiTwoGreen),
+ ),
+ child: Builder(
+ builder: (context) => Scaffold(
+ backgroundColor: _theme.backgroundColor,
+ body: _chatListController == null
+ ? const Center(child: CircularProgressIndicator())
+ : ChatViewList(
+ backgroundColor: _theme.backgroundColor,
+ controller: _chatListController!,
+ header: _buildHeader(),
+ footer: _buildFooter(),
+ appbar: CupertinoSliverNavigationBar(
+ largeTitle: Text(
+ 'Chats',
+ style: TextStyle(color: _theme.textColor),
+ ),
+ border: const Border.fromBorderSide(BorderSide.none),
+ backgroundColor: _theme.backgroundColor,
+ leading: PopupMenuButton(
+ style: const ButtonStyle(
+ tapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ icon: CircleAvatar(
+ radius: 14,
+ backgroundColor: _theme.iconButton,
+ child: SvgPicture.asset(
+ AppIcons.moreHoriz,
+ width: 24,
+ height: 24,
+ colorFilter: ColorFilter.mode(
+ _theme.iconColor,
+ BlendMode.srcIn,
+ ),
+ ),
+ ),
+ itemBuilder: (context) => [
+ PopupMenuItem(
+ value: 'dark_theme',
+ child:
+ Text(' ${_isDarkTheme ? 'Light' : 'Dark'} Mode'),
+ ),
+ ],
+ onSelected: (value) {
+ switch (value) {
+ case 'dark_theme':
+ _onThemeIconTap();
+ }
+ },
+ ),
+ trailing: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ GestureDetector(
+ onTap: () => showSnackBar('Camera Button Tapped'),
+ child: CircleAvatar(
+ radius: 14,
+ backgroundColor: _theme.iconButton,
+ child: SvgPicture.asset(
+ AppIcons.camera,
+ width: 24,
+ height: 24,
+ colorFilter: ColorFilter.mode(
+ _theme.iconColor,
+ BlendMode.srcIn,
+ ),
+ ),
+ ),
+ ),
+ const SizedBox(width: 16),
+ GestureDetector(
+ onTap: () => showSnackBar('Add Button Tapped'),
+ child: CircleAvatar(
+ radius: 14,
+ backgroundColor: AppColors.uiTwoGreen,
+ child: SvgPicture.asset(
+ AppIcons.add,
+ width: 24,
+ height: 24,
+ colorFilter: ColorFilter.mode(
+ _theme.backgroundColor,
+ BlendMode.srcIn,
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ separatorBuilder: (_, __) => Divider(
+ height: 1,
+ indent: 80,
+ endIndent: 0,
+ color: _theme.divider,
+ ),
+ 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(
+ lastMessageIconColor: _theme.iconColor,
+ onTap: (value) => Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (_) => ExampleTwoChatScreen(chat: value),
+ ),
+ ),
+ padding: const EdgeInsets.symmetric(
+ horizontal: 16,
+ vertical: 10,
+ ),
+ showUserActiveStatusIndicator: false,
+ userAvatarConfig: const UserAvatarConfig(
+ radius: 28,
+ backgroundColor: Color(0xFFD0FECF),
+ ),
+ lastMessageStatusConfig: LastMessageStatusConfig(
+ statusBuilder: (status) => switch (status) {
+ MessageStatus.read => SvgPicture.asset(
+ AppIcons.checkMark,
+ width: 19,
+ height: 19,
+ ),
+ MessageStatus.delivered => SvgPicture.asset(
+ AppIcons.checkMark,
+ width: 19,
+ height: 19,
+ colorFilter: const ColorFilter.mode(
+ AppColors.uiTwoGrey,
+ BlendMode.srcIn,
+ ),
+ ),
+ MessageStatus.pending => const Icon(
+ Icons.schedule,
+ size: 19,
+ color: AppColors.uiTwoGrey,
+ ),
+ MessageStatus.undelivered => const Icon(
+ Icons.error_rounded,
+ size: 19,
+ color: Colors.red,
+ ),
+ },
+ ),
+ pinIconConfig: PinIconConfig(
+ widget: SvgPicture.asset(AppIcons.pinned),
+ ),
+ typingStatusConfig: const TypingStatusConfig(
+ textStyle: TextStyle(
+ fontStyle: FontStyle.italic,
+ color: Color(0xff767779),
+ fontSize: 14,
+ ),
+ ),
+ timeConfig: const LastMessageTimeConfig(
+ textStyle: TextStyle(
+ color: Color(0xff767779),
+ fontSize: 14,
+ ),
+ ),
+ unreadCountConfig: UnreadCountConfig(
+ backgroundColor: AppColors.uiTwoGreen,
+ style: UnreadCountStyle.ninetyNinePlus,
+ textStyle: TextStyle(color: _theme.backgroundColor),
+ ),
+ userNameTextStyle: TextStyle(
+ fontSize: 16,
+ color: _theme.textColor,
+ fontWeight: FontWeight.w600,
+ ),
+ lastMessageTextStyle: const TextStyle(
+ color: Color(0xff767779),
+ fontSize: 14,
+ ),
+ ),
+ searchConfig: SearchConfig(
+ textEditingController: _searchController,
+ hintText: 'Ask Meta AI or Search',
+ hintStyle: TextStyle(
+ fontSize: 16.4,
+ color: _theme.searchText,
+ fontWeight: FontWeight.w400,
+ ),
+ textFieldBackgroundColor: _theme.searchBg,
+ prefixIcon: Icon(
+ Icons.search,
+ color: _theme.searchText,
+ size: 24,
+ ),
+ borderRadius: const BorderRadius.all(Radius.circular(10)),
+ contentPadding: const EdgeInsets.symmetric(
+ vertical: 0,
+ horizontal: 16,
+ ),
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ textStyle: TextStyle(color: _theme.textColor),
+ clearIcon: Icon(
+ Icons.clear,
+ color: _theme.iconColor,
+ size: 24,
+ ),
+ onSearch: (value) async {
+ if (value.isEmpty) {
+ return null;
+ }
+
+ List chats =
+ _chatListController?.chatListMap.values.toList() ??
+ [];
+
+ final list = chats
+ .where((chat) => chat.name
+ .toLowerCase()
+ .contains(value.toLowerCase()))
+ .toList();
+ return list;
+ },
+ ),
+ ),
+ bottomNavigationBar: _buildBottomNavigationBar(),
+ floatingActionButton: GestureDetector(
+ onTap: () => showSnackBar('Meta AI Button Tapped'),
+ child: Container(
+ width: 46,
+ height: 46,
+ padding: const EdgeInsetsGeometry.all(8),
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: _theme.floatingButton,
+ boxShadow: const [
+ BoxShadow(
+ color: Colors.black12,
+ blurRadius: 10,
+ spreadRadius: 10,
+ offset: Offset(0, 6),
+ ),
+ ],
+ ),
+ child: SvgPicture.asset(AppIcons.ai),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ void _onThemeIconTap() {
+ setState(() {
+ if (_isDarkTheme) {
+ _theme = ChatViewListTheme.uiTwoLight;
+ _isDarkTheme = false;
+ } else {
+ _theme = ChatViewListTheme.uiTwoDark;
+ _isDarkTheme = true;
+ }
+ });
+ }
+
+ Widget _buildFooter() {
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 19),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ SvgPicture.asset(AppIcons.lock),
+ const SizedBox(width: 3),
+ const Text.rich(
+ TextSpan(
+ text: 'Your personal messages are ',
+ children: [
+ TextSpan(
+ text: 'end-to-end encrypted',
+ style: TextStyle(color: AppColors.uiTwoGreen),
+ ),
+ ],
+ style: TextStyle(
+ fontSize: 11,
+ color: AppColors.uiTwoGrey,
+ fontWeight: FontWeight.normal,
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildHeader() {
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const SizedBox(height: 16),
+ GestureDetector(
+ onTap: () => Navigator.pushReplacement(
+ context,
+ MaterialPageRoute(
+ builder: (context) => const ExampleOneListScreen(),
+ ),
+ ),
+ child: Text(
+ '✨ Check out another UI',
+ style: TextStyle(
+ color: _theme.textColor,
+ shadows: [
+ Shadow(
+ color: _isDarkTheme ? Colors.white54 : Colors.black54,
+ offset: const Offset(0, -1),
+ blurRadius: 1,
+ ),
+ ],
+ decorationColor: _theme.textColor,
+ decoration: TextDecoration.underline,
+ ),
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.only(top: 16.0, bottom: 8),
+ child: SizedBox(
+ height: 34,
+ child: ListView(
+ scrollDirection: Axis.horizontal,
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ children: [
+ _buildFilterChip('All'),
+ const SizedBox(width: 8),
+ _buildFilterChip('Unread'),
+ const SizedBox(width: 8),
+ _buildFilterChip('Favourites'),
+ const SizedBox(width: 8),
+ _buildFilterChip('Groups'),
+ const SizedBox(width: 8),
+ _buildAddFilterChip(),
+ ],
+ ),
+ ),
+ ),
+ _archiveWidget(),
+ Divider(
+ height: 1,
+ indent: 80,
+ endIndent: 0,
+ color: _theme.divider,
+ ),
+ ],
+ );
+ }
+
+ Widget _buildFilterChip(String label) {
+ final isSelected = _selectedFilter == label;
+ return ChoiceChip(
+ label: Text(label),
+ selected: isSelected,
+ showCheckmark: false,
+ onSelected: (selected) {
+ if (selected) {
+ _onFilterSelected(label);
+ }
+ },
+ backgroundColor: _theme.chipBg,
+ selectedColor: _theme.selectedChipBg,
+ labelStyle: TextStyle(
+ color: isSelected ? _theme.selectedChip : _theme.chipText,
+ fontWeight: FontWeight.w600,
+ fontSize: 14,
+ ),
+ shape: const StadiumBorder(),
+ side: BorderSide.none,
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ visualDensity: VisualDensity.compact,
+ );
+ }
+
+ Widget _buildAddFilterChip() {
+ return InkWell(
+ onTap: () => showSnackBar('Add Filter Tapped'),
+ borderRadius: const BorderRadius.all(Radius.circular(19)),
+ child: Container(
+ width: 34,
+ height: 34,
+ decoration: BoxDecoration(
+ color: _theme.chipBg,
+ borderRadius: const BorderRadius.all(Radius.circular(19)),
+ ),
+ child: Icon(
+ Icons.add,
+ size: 20,
+ color: _theme.chipText,
+ ),
+ ),
+ );
+ }
+
+ Widget _buildBottomNavigationBar() {
+ return BottomNavigationBar(
+ backgroundColor: _theme.backgroundColor,
+ items: [
+ BottomNavigationBarItem(
+ icon: SvgPicture.asset(
+ AppIcons.status,
+ colorFilter: const ColorFilter.mode(
+ AppColors.uiTwoGrey,
+ BlendMode.srcIn,
+ ),
+ ),
+ label: 'Updates',
+ ),
+ BottomNavigationBarItem(
+ icon: SvgPicture.asset(
+ AppIcons.calls,
+ colorFilter:
+ const ColorFilter.mode(AppColors.uiTwoGrey, BlendMode.srcIn),
+ ),
+ label: 'Calls',
+ ),
+ BottomNavigationBarItem(
+ icon: SvgPicture.asset(
+ AppIcons.community,
+ colorFilter:
+ const ColorFilter.mode(AppColors.uiTwoGrey, BlendMode.srcIn),
+ ),
+ label: 'Communities',
+ ),
+ BottomNavigationBarItem(
+ icon: SvgPicture.asset(
+ AppIcons.messages,
+ colorFilter:
+ const ColorFilter.mode(AppColors.uiTwoGrey, BlendMode.srcIn),
+ ),
+ activeIcon: Badge(
+ label: const Text('1'),
+ offset: const Offset(10, 0),
+ backgroundColor: AppColors.uiTwoGreen,
+ child: SvgPicture.asset(
+ AppIcons.messages,
+ colorFilter: ColorFilter.mode(_theme.iconColor, BlendMode.srcIn),
+ ),
+ ),
+ label: 'Chats',
+ ),
+ BottomNavigationBarItem(
+ icon: SvgPicture.asset(
+ AppIcons.settings,
+ colorFilter:
+ const ColorFilter.mode(AppColors.uiTwoGrey, BlendMode.srcIn),
+ ),
+ label: 'Settings',
+ ),
+ ],
+ currentIndex: 3,
+ selectedItemColor: _theme.textColor,
+ unselectedItemColor: AppColors.uiTwoGrey,
+ showUnselectedLabels: true,
+ onTap: (index) {},
+ type: BottomNavigationBarType.fixed,
+ );
+ }
+
+ Widget _archiveWidget() {
+ return Padding(
+ padding: const EdgeInsets.only(top: 10, left: 32, bottom: 10, right: 32),
+ child: Row(
+ children: [
+ SvgPicture.asset(
+ AppIcons.archived,
+ width: 24,
+ height: 24,
+ colorFilter: ColorFilter.mode(
+ _theme.searchText,
+ BlendMode.srcIn,
+ ),
+ ),
+ const SizedBox(width: 28.66),
+ Expanded(
+ child: Text(
+ 'Archived',
+ style: TextStyle(
+ fontFamily: 'SF Pro Text',
+ fontWeight: FontWeight.w600,
+ fontSize: 16,
+ color: _theme.searchText,
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ void _onFilterSelected(String filter) {
+ setState(() {
+ _selectedFilter = filter;
+ });
+
+ if (filter == 'All') {
+ _chatListController?.clearSearch();
+ return;
+ }
+
+ final List filteredList;
+ switch (filter) {
+ case 'Unread':
+ filteredList = _chatListController?.chatListMap.values
+ .where((chat) => (chat.unreadCount ?? 0) > 0)
+ .toList() ??
+ [];
+ break;
+ case 'Favourites':
+ filteredList = _chatListController?.chatListMap.values
+ .where((chat) => chat.settings.pinStatus.isPinned)
+ .toList() ??
+ [];
+ break;
+ case 'Groups':
+ filteredList = _chatListController?.chatListMap.values
+ .where((chat) => chat.chatRoomType == ChatRoomType.group)
+ .toList() ??
+ [];
+ break;
+ default:
+ filteredList = _chatListController?.chatListMap.values.toList() ?? [];
+ break;
+ }
+ _chatListController?.setSearchChats(filteredList);
+ }
+
+ void showSnackBar(String message) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text(message)),
+ );
+ }
+
+ @override
+ void dispose() {
+ _chatListController?.dispose();
+ _searchController.dispose();
+ super.dispose();
+ }
+}
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 58fb0a8e..f994bf81 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -4,6 +4,7 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:intl/intl.dart';
import 'data.dart';
+import 'example_two/example_two_list_screen.dart';
import 'models/chatview_list_theme.dart';
import 'models/chatview_theme.dart';
import 'values/colors.dart';
@@ -22,12 +23,12 @@ class Example extends StatelessWidget {
title: 'Flutter Chat UI Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
- primaryColor: AppColors.instaPurple,
- colorScheme: ColorScheme.fromSwatch(accentColor: AppColors.instaPurple),
+ primaryColor: AppColors.uiOnePurple,
+ colorScheme: ColorScheme.fromSwatch(accentColor: AppColors.uiOnePurple),
),
darkTheme: ThemeData(
- primaryColor: AppColors.instaPurple,
- colorScheme: ColorScheme.fromSwatch(accentColor: AppColors.instaPurple),
+ primaryColor: AppColors.uiOnePurple,
+ colorScheme: ColorScheme.fromSwatch(accentColor: AppColors.uiOnePurple),
),
home: const ExampleOneListScreen(),
);
@@ -63,10 +64,6 @@ class _ExampleOneListScreenState extends State {
header: _headerWidget(),
appbar: ChatViewListAppBar(
backgroundColor: _theme.backgroundColor,
- leading: Icon(
- Icons.arrow_back_ios_rounded,
- color: _theme.iconColor,
- ),
centerTitle: false,
scrolledUnderElevation: 0,
titleText: 'ChatViewList',
@@ -203,7 +200,7 @@ class _ExampleOneListScreenState extends State {
_theme = ChatViewListTheme.uiOneLight;
_isDarkTheme = false;
} else {
- _theme = ChatViewListTheme.uiOneDart;
+ _theme = ChatViewListTheme.uiOneDark;
_isDarkTheme = true;
}
});
@@ -217,14 +214,14 @@ class _ExampleOneListScreenState extends State {
if (highlight) ...[
const CircleAvatar(
radius: 4,
- backgroundColor: AppColors.instaUnreadCountDot,
+ backgroundColor: AppColors.uiOneUnreadCountDot,
),
const SizedBox(width: 12),
],
SvgPicture.asset(
AppIcons.camera2,
colorFilter: ColorFilter.mode(
- highlight ? _theme.iconColor : AppColors.instaDarkGrey,
+ highlight ? _theme.iconColor : AppColors.uiOneDarkGrey,
BlendMode.srcIn,
),
),
@@ -233,30 +230,58 @@ class _ExampleOneListScreenState extends State {
}
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,
- ),
- overflow: TextOverflow.ellipsis,
+ return Column(
+ children: [
+ const SizedBox(height: 16),
+ GestureDetector(
+ onTap: () => Navigator.pushReplacement(
+ context,
+ MaterialPageRoute(
+ builder: (context) => const ExampleTwoListScreen(),
),
),
- Text(
- 'Requests',
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- style: TextStyle(color: _theme.searchText),
+ child: Text(
+ '✨ Check out another UI',
+ style: TextStyle(
+ color: _theme.textColor,
+ shadows: [
+ Shadow(
+ color: _isDarkTheme ? Colors.white54 : Colors.black54,
+ offset: const Offset(0, -1),
+ blurRadius: 1,
+ ),
+ ],
+ decorationColor: _theme.textColor,
+ decoration: TextDecoration.underline,
+ ),
),
- ],
- ),
+ ),
+ 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,
+ ),
+ overflow: TextOverflow.ellipsis,
+ ),
+ ),
+ Text(
+ 'Requests',
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ style: TextStyle(color: _theme.searchText),
+ ),
+ ],
+ ),
+ ),
+ ],
);
}
@@ -597,7 +622,7 @@ class _ExampleOneChatScreenState extends State {
),
),
sendButtonStyle: IconButton.styleFrom(
- backgroundColor: AppColors.instaPurple,
+ backgroundColor: AppColors.uiOnePurple,
padding: const EdgeInsets.symmetric(horizontal: 16),
),
textFieldConfig: TextFieldConfiguration(
@@ -619,7 +644,7 @@ class _ExampleOneChatScreenState extends State {
color: Colors.white,
),
style: IconButton.styleFrom(
- backgroundColor: AppColors.instaPurple,
+ backgroundColor: AppColors.uiOnePurple,
),
onPressed: (path, replyMessage) {
if (path?.isEmpty ?? true) return;
@@ -658,7 +683,7 @@ class _ExampleOneChatScreenState extends State {
color: Colors.white,
),
style: IconButton.styleFrom(
- backgroundColor: AppColors.instaPurple,
+ backgroundColor: AppColors.uiOnePurple,
),
onPressed: () =>
ScaffoldMessenger.of(context).showSnackBar(
@@ -735,7 +760,7 @@ class _ExampleOneChatScreenState extends State {
color: Colors.white,
),
),
- color: AppColors.instaPurple,
+ color: AppColors.uiOnePurple,
textStyle: TextStyle(color: Colors.white, fontSize: 16),
receiptsWidgetConfig: ReceiptsWidgetConfig(
showReceiptsIn: ShowReceiptsIn.lastMessage,
@@ -1025,17 +1050,17 @@ class _ExampleOneChatScreenState extends State {
children: [
CircleAvatar(
radius: 2.75,
- backgroundColor: AppColors.instaDarkGrey,
+ backgroundColor: AppColors.uiOneDarkGrey,
),
SizedBox(width: 3),
CircleAvatar(
radius: 2.75,
- backgroundColor: AppColors.instaDarkGrey,
+ backgroundColor: AppColors.uiOneDarkGrey,
),
SizedBox(width: 3),
CircleAvatar(
radius: 2.75,
- backgroundColor: AppColors.instaDarkGrey,
+ backgroundColor: AppColors.uiOneDarkGrey,
),
],
),
diff --git a/example/lib/models/chatview_list_theme.dart b/example/lib/models/chatview_list_theme.dart
index 29f9ac64..984a756b 100644
--- a/example/lib/models/chatview_list_theme.dart
+++ b/example/lib/models/chatview_list_theme.dart
@@ -9,6 +9,13 @@ class ChatViewListTheme {
required this.lastMessageText,
required this.backgroundColor,
required this.secondaryBg,
+ required this.iconButton,
+ required this.divider,
+ required this.floatingButton,
+ required this.selectedChip,
+ required this.selectedChipBg,
+ this.chipBg = Colors.transparent,
+ this.chipText = Colors.black,
});
final Color textColor;
@@ -18,24 +25,75 @@ class ChatViewListTheme {
final Color lastMessageText;
final Color backgroundColor;
final Color secondaryBg;
+ final Color iconButton;
+ final Color divider;
+ final Color floatingButton;
+ final Color selectedChip;
+ final Color selectedChipBg;
+ final Color chipBg;
+ final Color chipText;
- static const ChatViewListTheme uiOneDart = ChatViewListTheme(
+ static const ChatViewListTheme uiOneDark = ChatViewListTheme(
iconColor: Colors.white,
+ divider: Colors.white,
+ iconButton: Colors.white,
textColor: Colors.white,
searchBg: Color(0xff26292E),
searchText: Color(0xffA7ADB6),
lastMessageText: Color(0xff74787F),
backgroundColor: Color(0xff0B1014),
secondaryBg: Color(0xff26292E),
+ floatingButton: Colors.white,
+ selectedChip: Colors.black,
+ selectedChipBg: Colors.white,
+ );
+
+ static const ChatViewListTheme uiTwoDark = ChatViewListTheme(
+ iconColor: Colors.white,
+ iconButton: Color(0xff222222),
+ textColor: Colors.white,
+ searchBg: Color(0xff222222),
+ searchText: Color(0xff969494),
+ lastMessageText: Color(0xff74787F),
+ backgroundColor: Color(0xff0A0A0A),
+ secondaryBg: Color(0xff26292E),
+ divider: Color(0xff212121),
+ floatingButton: Color(0xFF242626),
+ selectedChipBg: Color(0xFF1A342A),
+ selectedChip: Color(0xFFE0FCD6),
+ chipBg: Color(0xFF161717),
+ chipText: Color(0xFF969595),
+ );
+
+ static const ChatViewListTheme uiTwoLight = ChatViewListTheme(
+ iconColor: Colors.black,
+ iconButton: Color(0x080A0A0A),
+ textColor: Colors.black,
+ searchBg: Color(0xFFF4F4F4),
+ searchText: Color(0xff767779),
+ lastMessageText: Colors.black,
+ backgroundColor: Color(0xffFEFFFE),
+ secondaryBg: Color(0xffF3F5F7),
+ divider: Color(0x33000000),
+ floatingButton: Color(0xFFF5F2EB),
+ selectedChipBg: Color(0xFFD0FECF),
+ selectedChip: Color(0xFF15603E),
+ chipBg: Color(0xFFF4F4F4),
+ chipText: Color(0xFF767779),
);
static const ChatViewListTheme uiOneLight = ChatViewListTheme(
iconColor: Colors.black,
+ divider: Colors.black,
+ iconButton: Colors.black,
textColor: Colors.black,
searchBg: Color(0xffF3F4F7),
searchText: Color(0xff5D636E),
lastMessageText: Colors.black,
backgroundColor: Color(0xffFEFFFE),
secondaryBg: Color(0xffF3F5F7),
+ floatingButton: Colors.black,
+ selectedChip: Colors.white,
+ selectedChipBg: Colors.black,
);
}
diff --git a/example/lib/values/borders.dart b/example/lib/values/borders.dart
new file mode 100644
index 00000000..bc8b73a8
--- /dev/null
+++ b/example/lib/values/borders.dart
@@ -0,0 +1,11 @@
+import 'package:flutter/material.dart';
+
+class AppBorders {
+ static const Border exampleTwoMessageBorder = Border.fromBorderSide(
+ BorderSide(
+ width: 0.66,
+ color: Color(0x0F000000),
+ strokeAlign: BorderSide.strokeAlignOutside,
+ ),
+ );
+}
diff --git a/example/lib/values/colors.dart b/example/lib/values/colors.dart
index fc88ea58..a9e521ba 100644
--- a/example/lib/values/colors.dart
+++ b/example/lib/values/colors.dart
@@ -1,16 +1,14 @@
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);
+ static const Color uiTwoGreen = Color(0xff1DAB61);
+ static const Color uiTwoGrey = Color(0xff767779);
+ static const Color uiTwoBackground = Color(0xFFF5F2EB);
+ static const Color uiTwoReplyLineColor = Color(0xffD42A66);
+ static const Color uiTwoSenderBgColor = Color(0xffD0FECF);
+ static const Color uiOneDarkGrey = Color(0xff767779);
+ static const Color uiOnePurple = Color(0xFF574FF0);
+ static const Color uiOneUnreadCountDot = Color(0xff5C6BF6);
}
diff --git a/example/lib/values/icons.dart b/example/lib/values/icons.dart
index ccf0e5da..9a86368e 100644
--- a/example/lib/values/icons.dart
+++ b/example/lib/values/icons.dart
@@ -1,5 +1,7 @@
class AppIcons {
+ static const String camera = 'assets/vectors/camera.svg';
static const String camera2 = 'assets/vectors/camera_2.svg';
+ static const String cameraOutline = "assets/vectors/camera_outline.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";
@@ -8,4 +10,17 @@ class AppIcons {
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";
+ static const String add = 'assets/vectors/add.svg';
+ static const String calls = "assets/vectors/calls.svg";
+ static const String archived = "assets/vectors/archived.svg";
+ static const String closeCircular = "assets/vectors/close_circular.svg";
+ static const String plus = 'assets/vectors/plus.svg';
+ static const String community = "assets/vectors/community.svg";
+ static const String lock = "assets/vectors/lock.svg";
+ static const String status = "assets/vectors/status.svg";
+ static const String messages = "assets/vectors/messages.svg";
+ static const String settings = "assets/vectors/settings.svg";
+ static const String mic = "assets/vectors/mic.svg";
+ static const String send = "assets/vectors/send.svg";
+ static const String moreHoriz = 'assets/vectors/more_horizontal.svg';
}
diff --git a/example/lib/values/images.dart b/example/lib/values/images.dart
new file mode 100644
index 00000000..b93fe83f
--- /dev/null
+++ b/example/lib/values/images.dart
@@ -0,0 +1,3 @@
+class AppImages {
+ static const String wpChatBackground = "assets/images/chat_background.png";
+}
diff --git a/example/lib/widgets/reply_message_tile.dart b/example/lib/widgets/reply_message_tile.dart
new file mode 100644
index 00000000..f54d566d
--- /dev/null
+++ b/example/lib/widgets/reply_message_tile.dart
@@ -0,0 +1,106 @@
+import 'package:audio_waveforms/audio_waveforms.dart';
+import 'package:chatview/chatview.dart';
+import 'package:flutter/material.dart';
+
+import '../../values/colors.dart';
+
+class ReplyMessageTile extends StatelessWidget {
+ const ReplyMessageTile({
+ required this.replyMessage,
+ required this.chatController,
+ super.key,
+ });
+
+ final ReplyMessage? replyMessage;
+ final ChatController chatController;
+
+ @override
+ Widget build(BuildContext context) {
+ const textStyle = TextStyle(
+ fontSize: 12,
+ fontWeight: FontWeight.w400,
+ height: 1.33,
+ color: Color(0xFF232626),
+ );
+ final reply = replyMessage;
+ if (reply == null) {
+ return const SizedBox.shrink();
+ }
+ final replyBySender = reply.replyBy == chatController.currentUser.id;
+ final messagedUser = chatController.getUserFromId(reply.replyBy);
+ final replyBy =
+ replyBySender ? PackageStrings.currentLocale.you : messagedUser.name;
+ return Container(
+ constraints: BoxConstraints(
+ maxWidth: MediaQuery.of(context).size.width * 0.75,
+ ),
+ margin: const EdgeInsets.only(bottom: 6),
+ decoration: BoxDecoration(
+ color: replyBySender ? AppColors.uiTwoSenderBgColor : Colors.white,
+ borderRadius: const BorderRadius.all(Radius.circular(12)),
+ ),
+ child: Container(
+ padding: const EdgeInsets.fromLTRB(9, 9.5, 9, 10.5),
+ decoration: const BoxDecoration(
+ color: Color(0x0A0A0A0A),
+ borderRadius: BorderRadius.all(Radius.circular(8)),
+ border: Border(
+ left: BorderSide(color: AppColors.uiTwoReplyLineColor, width: 4),
+ ),
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ replyBy,
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ style: const TextStyle(
+ fontSize: 14,
+ height: 1.36,
+ letterSpacing: -0.01,
+ fontWeight: FontWeight.w600,
+ color: AppColors.uiTwoReplyLineColor,
+ ),
+ ),
+ const SizedBox(height: 2),
+ switch (reply.messageType) {
+ MessageType.voice => Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const Icon(Icons.mic),
+ const SizedBox(width: 4),
+ if (reply.voiceMessageDuration != null)
+ Text(
+ reply.voiceMessageDuration!.toHHMMSS(),
+ style: textStyle,
+ ),
+ ],
+ ),
+ MessageType.image => Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Icon(
+ Icons.photo,
+ size: 20,
+ color: Colors.grey.shade700,
+ ),
+ Text(
+ PackageStrings.currentLocale.photo,
+ style: textStyle,
+ ),
+ ],
+ ),
+ MessageType.custom || MessageType.text => Text(
+ reply.message,
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ style: textStyle,
+ ),
+ },
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/example/lib/widgets/wp_custom_chat_bar.dart b/example/lib/widgets/wp_custom_chat_bar.dart
new file mode 100644
index 00000000..f3e0a52c
--- /dev/null
+++ b/example/lib/widgets/wp_custom_chat_bar.dart
@@ -0,0 +1,399 @@
+import 'dart:io';
+
+import 'package:chatview/chatview.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:audio_waveforms/audio_waveforms.dart';
+
+import '../../values/colors.dart';
+import '../../values/icons.dart';
+
+class WpCustomChatBar extends StatefulWidget {
+ const WpCustomChatBar({
+ required this.chatController,
+ required this.replyMessage,
+ this.onAttachPressed,
+ super.key,
+ });
+
+ final ChatController chatController;
+ final ReplyMessage replyMessage;
+ final VoidCallback? onAttachPressed;
+
+ @override
+ State createState() => _WpCustomChatBarState();
+}
+
+class _WpCustomChatBarState extends State {
+ RecorderController? controller;
+ late ReplyMessage? _replyMessage = widget.replyMessage;
+ final voiceRecordingConfig = const VoiceRecordingConfiguration();
+ final isRecording = ValueNotifier(false);
+ final _focusNode = FocusNode();
+ final _textController = TextEditingController();
+ final _hasTextNotifier = ValueNotifier(false);
+
+ @override
+ void initState() {
+ super.initState();
+ if (!kIsWeb && (Platform.isIOS || Platform.isAndroid)) {
+ controller = RecorderController();
+ }
+ _textController.addListener(_onTextChanged);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ if (_replyMessage != null) {
+ _replyMessage = widget.replyMessage;
+ }
+ final repliedUser = _replyMessage?.replyTo.isNotEmpty ?? false
+ ? widget.chatController.getUserFromId(_replyMessage?.replyTo ?? '')
+ : null;
+ String replyTo =
+ _replyMessage?.replyTo == widget.chatController.currentUser.id
+ ? PackageStrings.currentLocale.you
+ : repliedUser?.name ?? '';
+ return Container(
+ color: AppColors.uiTwoBackground,
+ child: SafeArea(
+ top: false,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ if (_replyMessage?.message.isNotEmpty ?? false)
+ Container(
+ padding: const EdgeInsets.fromLTRB(8, 8, 7.5, 7.5),
+ decoration: const BoxDecoration(
+ border: Border(
+ left: BorderSide(color: AppColors.uiTwoReplyLineColor, width: 4),
+ ),
+ ),
+ child: Row(
+ children: [
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ replyTo,
+ style: const TextStyle(
+ fontStyle: FontStyle.normal,
+ fontWeight: FontWeight.w600,
+ fontSize: 14,
+ height: 1.3571428571,
+ letterSpacing: -0.01,
+ color: Color(0xFFD42A66),
+ ),
+ ),
+ const SizedBox(height: 1.5),
+ Text(
+ _replyMessage?.message ?? '',
+ style: const TextStyle(
+ fontSize: 12,
+ height: 1.33,
+ color: Color(0xFF0A0A0A),
+ ),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(width: 16),
+ SizedBox.square(
+ dimension: 32,
+ child: IconButton(
+ onPressed: () => ChatView.closeReplyMessageView(
+ context,
+ ),
+ padding: EdgeInsets.zero,
+ icon: SvgPicture.asset(AppIcons.closeCircular),
+ ),
+ ),
+ ],
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.fromLTRB(7, 5.5, 9, 5.5),
+ child: ValueListenableBuilder(
+ valueListenable: isRecording,
+ builder: (context, isRecordingValue, _) => Row(
+ children: [
+ if (!isRecordingValue) ...[
+ SizedBox.square(
+ dimension: 32,
+ child: IconButton(
+ padding: EdgeInsets.zero,
+ onPressed: widget.onAttachPressed,
+ icon: SvgPicture.asset(AppIcons.plus),
+ ),
+ ),
+ const SizedBox(width: 8),
+ ],
+ Expanded(
+ child: DecoratedBox(
+ decoration: const BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.all(Radius.circular(15)),
+ border: Border.fromBorderSide(
+ BorderSide(width: 0.33, color: Color(0xFFB2B2B2)),
+ ),
+ ),
+ child: Row(
+ children: [
+ if (isRecordingValue &&
+ controller != null &&
+ !kIsWeb)
+ Expanded(
+ child: AudioWaveforms(
+ size: const Size(double.maxFinite, 50),
+ recorderController: controller!,
+ margin: voiceRecordingConfig.margin,
+ padding: voiceRecordingConfig.padding ??
+ const EdgeInsets.symmetric(horizontal: 5),
+ decoration: voiceRecordingConfig.decoration ??
+ BoxDecoration(
+ color: voiceRecordingConfig
+ .backgroundColor,
+ borderRadius: BorderRadius.circular(12),
+ ),
+ waveStyle: voiceRecordingConfig.waveStyle ??
+ WaveStyle(
+ extendWaveform: true,
+ showMiddleLine: false,
+ waveColor: voiceRecordingConfig
+ .waveStyle?.waveColor ??
+ Colors.black,
+ ),
+ ),
+ )
+ else
+ Expanded(
+ child: TextField(
+ maxLines: null,
+ focusNode: _focusNode,
+ controller: _textController,
+ textAlignVertical: TextAlignVertical.bottom,
+ style: const TextStyle(
+ fontSize: 16,
+ letterSpacing: -0.02,
+ fontWeight: FontWeight.w400,
+ ),
+ decoration: const InputDecoration(
+ isDense: true,
+ border: InputBorder.none,
+ contentPadding:
+ EdgeInsets.fromLTRB(10, 4, 16, 4),
+ hintStyle: TextStyle(
+ color: Color(0xFF999999),
+ fontSize: 16,
+ ),
+ ),
+ onSubmitted: (_) => _sendMessage(),
+ ),
+ ),
+ if (!isRecordingValue) ...[
+ SizedBox.square(
+ dimension: 24,
+ child: EmojiPickerActionButton(
+ context: context,
+ style: IconButton.styleFrom(
+ padding: EdgeInsets.zero,
+ ),
+ icon: SvgPicture.asset(AppIcons.sticker),
+ onPressed: (emoji, replyMessage) {
+ if (emoji?.isEmpty ?? true) return;
+ _textController.text =
+ _textController.text + emoji!;
+ },
+ ),
+ ),
+ const SizedBox(width: 9),
+ ],
+ ],
+ ),
+ ),
+ ),
+ const SizedBox(width: 7),
+ AnimatedSize(
+ alignment: Alignment.centerRight,
+ curve: Curves.easeInOut,
+ duration: const Duration(milliseconds: 400),
+ child: ValueListenableBuilder(
+ valueListenable: _hasTextNotifier,
+ builder: (_, hasText, __) {
+ if (isRecordingValue) {
+ return Row(
+ children: [
+ SizedBox.square(
+ dimension: 32,
+ child: IconButton(
+ onPressed: _cancelRecording,
+ padding: EdgeInsets.zero,
+ icon: const Icon(
+ Icons.stop_circle_outlined),
+ ),
+ ),
+ const SizedBox(width: 7),
+ SizedBox.square(
+ dimension: 32,
+ child: IconButton(
+ onPressed: _recordOrStop,
+ padding: EdgeInsets.zero,
+ style: IconButton.styleFrom(
+ backgroundColor: AppColors.uiTwoGreen,
+ ),
+ icon: SvgPicture.asset(AppIcons.send),
+ ),
+ ),
+ ],
+ );
+ }
+ return Row(
+ children: hasText
+ ? [
+ SizedBox.square(
+ dimension: 32,
+ child: IconButton(
+ onPressed: _sendMessage,
+ padding: EdgeInsets.zero,
+ style: IconButton.styleFrom(
+ backgroundColor: AppColors.uiTwoGreen,
+ ),
+ icon:
+ SvgPicture.asset(AppIcons.send),
+ ),
+ ),
+ ]
+ : [
+ SizedBox.square(
+ dimension: 32,
+ child: CameraActionButton(
+ icon: SvgPicture.asset(
+ AppIcons.cameraOutline,
+ ),
+ style: IconButton.styleFrom(
+ padding: EdgeInsets.zero,
+ ),
+ onPressed: (path, replyMessage) {
+ if (path?.isEmpty ?? true) return;
+ ChatView.closeReplyMessageView(
+ context);
+ widget.chatController.addMessage(
+ Message(
+ id: DateTime.now()
+ .millisecondsSinceEpoch
+ .toString(),
+ message: path!,
+ createdAt: DateTime.now(),
+ messageType: MessageType.image,
+ replyMessage: _replyMessage ??
+ const ReplyMessage(),
+ sentBy: widget.chatController
+ .currentUser.id,
+ ),
+ );
+ },
+ ),
+ ),
+ if (!kIsWeb) ...[
+ const SizedBox(width: 7),
+ SizedBox.square(
+ dimension: 32,
+ child: IconButton(
+ onPressed: _recordOrStop,
+ padding: EdgeInsets.zero,
+ icon: SvgPicture.asset(
+ AppIcons.mic),
+ ),
+ ),
+ ],
+ ],
+ );
+ }),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ @override
+ void dispose() {
+ _textController.removeListener(_onTextChanged);
+ _textController.dispose();
+ _focusNode.dispose();
+ super.dispose();
+ }
+
+ void _onTextChanged() {
+ _hasTextNotifier.value = _textController.text.trim().isNotEmpty;
+ }
+
+ Future _cancelRecording() async {
+ if (!isRecording.value) return;
+ final path = await controller?.stop();
+ if (path == null) {
+ isRecording.value = false;
+ return;
+ }
+ final file = File(path);
+
+ if (await file.exists()) {
+ await file.delete();
+ }
+
+ isRecording.value = false;
+ }
+
+ void _sendMessage() {
+ final text = _textController.text.trim();
+ if (text.isNotEmpty) {
+ ChatView.closeReplyMessageView(context);
+ widget.chatController.addMessage(
+ Message(
+ id: DateTime.now().millisecondsSinceEpoch.toString(),
+ message: text,
+ createdAt: DateTime.now(),
+ replyMessage: _replyMessage ?? const ReplyMessage(),
+ messageType: MessageType.text,
+ sentBy: widget.chatController.currentUser.id,
+ ),
+ );
+ _textController.clear();
+ _hasTextNotifier.value = false;
+ }
+ }
+
+ Future _recordOrStop() async {
+ if (!isRecording.value) {
+ await controller?.record(
+ sampleRate: voiceRecordingConfig.sampleRate,
+ bitRate: voiceRecordingConfig.bitRate,
+ androidEncoder: voiceRecordingConfig.androidEncoder,
+ iosEncoder: voiceRecordingConfig.iosEncoder,
+ androidOutputFormat: voiceRecordingConfig.androidOutputFormat,
+ );
+ isRecording.value = true;
+ } else {
+ final path = await controller?.stop();
+ isRecording.value = false;
+ if (path?.isEmpty ?? true) return;
+ if (mounted) ChatView.closeReplyMessageView(context);
+ widget.chatController.addMessage(
+ Message(
+ id: DateTime.now().millisecondsSinceEpoch.toString(),
+ message: path!,
+ createdAt: DateTime.now(),
+ messageType: MessageType.voice,
+ replyMessage: _replyMessage ?? const ReplyMessage(),
+ sentBy: widget.chatController.currentUser.id,
+ ),
+ );
+ }
+ }
+}
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 f3f4564e..3be1f667 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
@@ -57,6 +57,7 @@ class ListTileConfig {
fontWeight: FontWeight.normal,
),
this.highlightTextStyle = const TextStyle(fontWeight: FontWeight.bold),
+ this.lastMessageIconColor = Colors.black,
this.userNameTextStyle,
this.onTap,
this.lastMessageTileBuilder,
@@ -91,6 +92,12 @@ class ListTileConfig {
/// Defaults to `1`.
final int? userNameMaxLines;
+ /// Color for icons used in the last message for message types
+ /// like image, voice.
+ ///
+ /// Defaults to `Colors.black`.
+ final Color lastMessageIconColor;
+
/// Text styles for the last message text in the user widget.
final TextStyle? lastMessageTextStyle;
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 38a9a748..13a3eeff 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
@@ -139,6 +139,7 @@ class ChatViewListItemTile extends StatelessWidget {
: ValueKey(lastMessage?.id),
unreadCount: unreadCount,
lastMessage: lastMessage!,
+ iconColor: config.lastMessageIconColor,
lastMessageType: lastMessage.messageType,
lastMessageMaxLines:
config.lastMessageMaxLines,
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 88ce648f..206039f9 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,
+ required this.iconColor,
this.highlightTextStyle,
this.lastMessageType,
this.lastMessageBuilder,
@@ -39,6 +40,7 @@ class LastMessageView extends StatelessWidget {
final bool showStatusIcon;
final TextStyle? highlightTextStyle;
final LastMessageStatusConfig statusConfig;
+ final Color iconColor;
@override
Widget build(BuildContext context) {
@@ -66,7 +68,7 @@ class LastMessageView extends StatelessWidget {
MessageType.image => Row(
mainAxisSize: MainAxisSize.min,
children: [
- const Icon(Icons.photo, size: 14),
+ Icon(Icons.photo, size: 14, color: iconColor),
const SizedBox(width: 5),
Flexible(
child: Text(
@@ -92,7 +94,7 @@ class LastMessageView extends StatelessWidget {
MessageType.voice => Row(
mainAxisSize: MainAxisSize.min,
children: [
- const Icon(Icons.mic, size: 14),
+ Icon(Icons.mic, size: 14, color: iconColor),
const SizedBox(width: 5),
Flexible(
child: Text(
diff --git a/pubspec.yaml b/pubspec.yaml
index 631d2af7..53b86451 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -23,7 +23,6 @@ dependencies:
chatview_utils:
git:
url: https://github.com/SimformSolutionsPvtLtd/chatview_utils
- ref: feature/update_changelog
emoji_picker_flutter: ^4.3.0
flutter: