Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add custom separators, read reciepts, timestamp with read status #58

Open
wants to merge 1 commit into
base: flutter-3.7
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/dash_chat_2.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ part 'src/models/cursor_style.dart';
part 'src/models/input_options.dart';
part 'src/models/mention.dart';
part 'src/models/message_list_options.dart';
part 'src/models/custom_separators.dart';
part 'src/models/message_options.dart';
part 'src/models/quick_reply.dart';
part 'src/models/quick_reply_options.dart';
Expand Down
6 changes: 6 additions & 0 deletions lib/src/models/chat_message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class ChatMessage {
ChatMessage({
required this.user,
required this.createdAt,
this.id,
this.text = '',
this.medias,
this.quickReplies,
Expand All @@ -17,6 +18,7 @@ class ChatMessage {
/// Create a ChatMessage instance from json data
factory ChatMessage.fromJson(Map<String, dynamic> jsonData) {
return ChatMessage(
id: (jsonData['messageId'] ?? jsonData['id']).toString(),
user: ChatUser.fromJson(jsonData['user'] as Map<String, dynamic>),
createdAt: DateTime.parse(jsonData['createdAt'].toString()).toLocal(),
text: jsonData['text']?.toString() ?? '',
Expand Down Expand Up @@ -46,6 +48,10 @@ class ChatMessage {
);
}

/// Unique id for a ChatMessage (optional because during sending a message
/// we might not have an id, maybe its generated on the server side)
String? id;

/// Text of the message (optional because you can also just send a media)
String text;

Expand Down
11 changes: 11 additions & 0 deletions lib/src/models/custom_separators.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
part of dash_chat_2;
// ignore_for_file: non_constant_identifier_names

class CustomSeparator {
CustomSeparator({
required this.shouldShowSeparator,
required this.separator,
});
bool Function(ChatMessage currentMessage) shouldShowSeparator;
Widget separator;
}
3 changes: 3 additions & 0 deletions lib/src/models/message_list_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class MessageListOptions {
this.showDateSeparator = true,
this.dateSeparatorFormat,
this.dateSeparatorBuilder,
this.customSeparators,
this.separatorFrequency = SeparatorFrequency.days,
this.scrollController,
this.chatFooterBuilder,
Expand All @@ -27,6 +28,8 @@ class MessageListOptions {
/// You can use DefaultDateSeparator to only override some variables
final Widget Function(DateTime date)? dateSeparatorBuilder;

final List<CustomSeparator>? customSeparators;

/// The frequency of the separator
final SeparatorFrequency separatorFrequency;

Expand Down
56 changes: 53 additions & 3 deletions lib/src/models/message_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class MessageOptions {
this.onPressAvatar,
this.onLongPressAvatar,
this.onLongPressMessage,
this.onTapDownMessage,
this.onPressMessage,
this.onPressMention,
this.currentUserContainerColor,
Expand All @@ -28,10 +29,24 @@ class MessageOptions {
this.textBeforeMedia = true,
this.onTapMedia,
this.showTime = false,
this.showStatus = false,
this.timeFormat,
this.messageTimeBuilder,
this.messageMediaBuilder,
});
this.borderRadius = 18.0,
Color? currentUserTimeTextColor,
Color? currentUserReadStatusIconColor,
this.marginDifferentAuthor = const EdgeInsets.only(top: 15),
this.marginSameAuthor = const EdgeInsets.only(top: 2),
this.spaceWhenAvatarIsHidden = 10.0,
this.timeFontSize = 10.0,
this.timePadding = const EdgeInsets.only(top: 5),
Color? timeTextColor,
}) : _currentUserContainerColor = currentUserContainerColor,
_currentUserTextColor = currentUserTextColor,
_currentUserTimeTextColor = currentUserTimeTextColor,
_currentUserReadStatusIconColor = currentUserReadStatusIconColor,
_timeTextColor = timeTextColor;

/// Format of the time if [showTime] is true
/// Default to: DateFormat('HH:mm')
Expand All @@ -40,6 +55,9 @@ class MessageOptions {
/// If you want to show the time under the text of each message
final bool showTime;

/// If you want to show the status under the text of each message
final bool showStatus;

/// If you want to show the avatar of the current user
final bool showCurrentUserAvatar;

Expand Down Expand Up @@ -69,6 +87,9 @@ class MessageOptions {
/// Function to call when the user long press on a message
final Function(ChatMessage)? onLongPressMessage;

/// Function to call when the user taps on a message
final Function(TapDownDetails, ChatMessage)? onTapDownMessage;

/// Function to call when the user press on a message
final Function(ChatMessage)? onPressMessage;

Expand All @@ -80,8 +101,37 @@ class MessageOptions {
final Color? currentUserContainerColor;

/// Color of the current user text in chat bubbles
/// Default to white
final Color? currentUserTextColor;
///
/// Default to: `Theme.of(context).colorScheme.onPrimary`
Color currentUserTextColor(BuildContext context) {
return _currentUserTextColor ?? Theme.of(context).colorScheme.onPrimary;
}


/// Used to calculate [currentUserTextColor]
final Color? _currentUserTextColor;

/// Color of current user time text in chat bubbles
///
/// Default to: `currentUserTextColor`
Color currentUserTimeTextColor(BuildContext context) {
return _currentUserTimeTextColor ?? currentUserTextColor(context);
}

/// Used to calculate [currentUserTimeTextColor]
final Color? _currentUserTimeTextColor;


/// Color of current user time text in chat bubbles
///
/// Default to: `currentUserTextColor`
Color currentUserReadStatusIconColor(BuildContext context) {
return _currentUserReadStatusIconColor ?? currentUserTextColor(context);
}

/// Used to calculate [currentUserReadStatusIconColor]
final Color? _currentUserReadStatusIconColor;


/// Color of the other users chat bubbles
/// Default to Colors.grey[100]
Expand Down
24 changes: 18 additions & 6 deletions lib/src/widgets/message_list/message_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,18 @@ class MessageListState extends State<MessageList> {
date: message.createdAt,
messageListOptions: widget.messageListOptions,
),
if(widget.messageListOptions.customSeparators != null)
...widget.messageListOptions.customSeparators!
.map<Widget>(
(CustomSeparator cs) {
return Visibility(
visible: cs.shouldShowSeparator(message),
child: Container(
margin: const EdgeInsets.symmetric(vertical: 5.0),
child: cs.separator
),
);
}).toList(),
if (widget.messageOptions.messageRowBuilder !=
null) ...<Widget>[
widget.messageOptions.messageRowBuilder!(
Expand Down Expand Up @@ -158,13 +170,13 @@ class MessageListState extends State<MessageList> {
if (!widget.scrollToBottomOptions.disabled && scrollToBottomIsVisible)
widget.scrollToBottomOptions.scrollToBottomBuilder != null
? widget.scrollToBottomOptions
.scrollToBottomBuilder!(scrollController)
.scrollToBottomBuilder!(scrollController)
: DefaultScrollToBottom(
scrollController: scrollController,
readOnly: widget.readOnly,
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
textColor: Theme.of(context).primaryColor,
),
scrollController: scrollController,
readOnly: widget.readOnly,
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
textColor: Theme.of(context).primaryColor,
),
],
),
);
Expand Down
69 changes: 49 additions & 20 deletions lib/src/widgets/message_row/default_message_text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,59 @@ class DefaultMessageText extends StatelessWidget {

@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment:
isOwnMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start,
return Stack(
children: <Widget>[
Wrap(
children: getMessage(),
verticalDirection: VerticalDirection.down,
alignment: WrapAlignment.end,
children: <Widget>[
...getMessage(context),
// for reserved text
if (isOwnMessage) const Text(' \u202F')
// here the icon for read receipt is generally not shown so we need lesser space
else const Text(' \u202F'),
],
),
if (messageOptions.showTime)
messageOptions.messageTimeBuilder != null
? messageOptions.messageTimeBuilder!(message, isOwnMessage)
: Padding(
padding: const EdgeInsets.only(top: 5),
child: Text(
(messageOptions.timeFormat ?? intl.DateFormat('HH:mm'))
.format(message.createdAt),
style: TextStyle(
color: isOwnMessage
? (messageOptions.currentUserTextColor ??
Colors.white70)
: (messageOptions.textColor ?? Colors.black54),
fontSize: 10,
Positioned(
bottom: 0,
right: 0,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
if (messageOptions.showTime)
messageOptions.messageTimeBuilder != null
? messageOptions.messageTimeBuilder!(message, isOwnMessage)
: Padding(
padding: messageOptions.timePadding,
child: Text(
(messageOptions.timeFormat ?? intl.DateFormat('HH:mm'))
.format(message.createdAt),
style: TextStyle(
color: isOwnMessage
? messageOptions.currentUserTimeTextColor(context)
: messageOptions.timeTextColor(),
fontSize: messageOptions.timeFontSize,
),
),
),
),
Visibility(
visible: isOwnMessage,
child: Container(
margin: const EdgeInsets.only(left: 5),
child: messageOptions.showStatus ?
message.status == MessageStatus.read ? Icon(
Icons.done_all, color: messageOptions.currentUserReadStatusIconColor(context), size: 15,
) :
message.status == MessageStatus.received ? Icon(
Icons.check, color: messageOptions.currentUserReadStatusIconColor(context), size: 15
) :
Container() : Container()
),
)
]
),
),
],
);
}
Expand All @@ -69,7 +98,7 @@ class DefaultMessageText extends StatelessWidget {
.forEach((String? part) {
if (mentionRegex.hasMatch(part!)) {
Mention mention = message.mentions!.firstWhere(
(Mention m) => m.title == part,
(Mention m) => m.title == part,
);
res.add(getMention(mention));
} else {
Expand Down
3 changes: 3 additions & 0 deletions lib/src/widgets/message_row/message_row.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ class MessageRow extends StatelessWidget {
if (!messageOptions.showOtherUsersAvatar)
const Padding(padding: EdgeInsets.only(left: 10)),
GestureDetector(
onTapDown: messageOptions.onTapDownMessage != null
? (TapDownDetails td) => messageOptions.onTapDownMessage!(td, message)
: null,
onLongPress: messageOptions.onLongPressMessage != null
? () => messageOptions.onLongPressMessage!(message)
: null,
Expand Down