refactor message widget
This commit is contained in:
@@ -22,43 +22,33 @@ class AttachmentTitle extends StatelessWidget {
|
||||
launchURL(context, attachment.titleLink);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
constraints: BoxConstraints.loose(
|
||||
Size(
|
||||
MediaQuery.of(context).size.width * 0.7,
|
||||
500,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
attachment.title,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: messageTheme.messageText.copyWith(
|
||||
color: StreamChatTheme.of(context).accentColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
attachment.title,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: messageTheme.messageText.copyWith(
|
||||
color: StreamChatTheme.of(context).accentColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
if (attachment.titleLink != null ||
|
||||
attachment.ogScrapeUrl != null)
|
||||
Text(
|
||||
Uri.parse(attachment.titleLink ?? attachment.ogScrapeUrl)
|
||||
.authority
|
||||
.split('.')
|
||||
.reversed
|
||||
.take(2)
|
||||
.toList()
|
||||
.reversed
|
||||
.join('.'),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: messageTheme.createdAt,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (attachment.titleLink != null || attachment.ogScrapeUrl != null)
|
||||
Text(
|
||||
Uri.parse(attachment.titleLink ?? attachment.ogScrapeUrl)
|
||||
.authority
|
||||
.split('.')
|
||||
.reversed
|
||||
.take(2)
|
||||
.toList()
|
||||
.reversed
|
||||
.join('.'),
|
||||
style: messageTheme.createdAt,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -44,7 +44,7 @@ class DateDivider extends StatelessWidget {
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 24.0),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
|
||||
@@ -5,30 +5,19 @@ class DeletedMessage extends StatelessWidget {
|
||||
const DeletedMessage({
|
||||
Key key,
|
||||
@required this.messageTheme,
|
||||
this.alignment,
|
||||
}) : super(key: key);
|
||||
|
||||
final MessageTheme messageTheme;
|
||||
final Alignment alignment;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
alignment: alignment,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 14,
|
||||
vertical: 14,
|
||||
),
|
||||
child: Text(
|
||||
'This message was deleted...',
|
||||
style: messageTheme.messageText.copyWith(
|
||||
fontStyle: FontStyle.italic,
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
),
|
||||
),
|
||||
return Text(
|
||||
'This message was deleted...',
|
||||
style: messageTheme.messageText.copyWith(
|
||||
fontStyle: FontStyle.italic,
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,10 +26,9 @@ class ImageAttachment extends StatelessWidget {
|
||||
attachment: attachment,
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Stack(
|
||||
children: <Widget>[
|
||||
@@ -42,7 +41,6 @@ class ImageAttachment extends StatelessWidget {
|
||||
return GestureDetector(
|
||||
child: Image(
|
||||
image: provider,
|
||||
width: MediaQuery.of(context).size.width * 0.7,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
onTap: () {
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:stream_chat_flutter/src/reaction_picker.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_channel.dart';
|
||||
|
||||
import '../stream_chat_flutter.dart';
|
||||
import 'message_input.dart';
|
||||
import 'stream_chat.dart';
|
||||
|
||||
class MessageActionsBottomSheet extends StatelessWidget {
|
||||
final Widget Function(BuildContext, Message) editMessageInputBuilder;
|
||||
final void Function(Message) onThreadTap;
|
||||
final Message message;
|
||||
final bool showReactions;
|
||||
final bool showDeleteMessage;
|
||||
final bool showEditMessage;
|
||||
final bool showReply;
|
||||
final Map<String, String> reactionToEmoji = const {
|
||||
'love': '❤️️',
|
||||
'haha': '😂',
|
||||
'like': '👍',
|
||||
'sad': '😕',
|
||||
'angry': '😡',
|
||||
'wow': '😲',
|
||||
};
|
||||
|
||||
const MessageActionsBottomSheet({
|
||||
Key key,
|
||||
this.message,
|
||||
this.showReactions,
|
||||
this.showDeleteMessage,
|
||||
this.showEditMessage,
|
||||
this.onThreadTap,
|
||||
this.showReply,
|
||||
this.editMessageInputBuilder,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final channel = StreamChannel.of(context).channel;
|
||||
return SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
if (showReactions &&
|
||||
(message.status == MessageSendingStatus.SENT ||
|
||||
message.status == null))
|
||||
ReactionPicker(
|
||||
channel: channel,
|
||||
reactionToEmoji: reactionToEmoji,
|
||||
message: message,
|
||||
),
|
||||
if (showDeleteMessage) _buildDeleteButton(context),
|
||||
if (showEditMessage) _buildEditMessage(context),
|
||||
if (showReply &&
|
||||
(message.status == MessageSendingStatus.SENT ||
|
||||
message.status == null) &&
|
||||
message.parentId == null)
|
||||
_buildReplyButton(context),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
FlatButton _buildDeleteButton(BuildContext context) {
|
||||
return FlatButton(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
'Delete message',
|
||||
style:
|
||||
Theme.of(context).textTheme.headline5.copyWith(color: Colors.red),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
StreamChat.of(context).client.deleteMessage(
|
||||
message,
|
||||
StreamChannel.of(context).channel.cid,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
FlatButton _buildEditMessage(BuildContext context) {
|
||||
return FlatButton(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
'Edit message',
|
||||
style: Theme.of(context).textTheme.headline5,
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
Navigator.pop(context);
|
||||
_showEditBottomSheet(context);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showEditBottomSheet(BuildContext context) {
|
||||
final channel = StreamChannel.of(context).channel;
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
elevation: 2,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
isScrollControlled: true,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(32),
|
||||
topRight: Radius.circular(32),
|
||||
),
|
||||
),
|
||||
builder: (context) {
|
||||
return StreamChannel(
|
||||
channel: channel,
|
||||
child: Flex(
|
||||
direction: Axis.vertical,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 16.0,
|
||||
left: 16.0,
|
||||
right: 16.0,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'Edit message',
|
||||
style: Theme.of(context).textTheme.headline6,
|
||||
),
|
||||
Container(
|
||||
height: 30,
|
||||
padding: const EdgeInsets.all(2.0),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: RawMaterialButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
elevation: 0,
|
||||
highlightElevation: 0,
|
||||
focusElevation: 0,
|
||||
disabledElevation: 0,
|
||||
hoverElevation: 0,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
fillColor:
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white.withOpacity(.1)
|
||||
: Colors.black.withOpacity(.1),
|
||||
padding: EdgeInsets.all(4),
|
||||
child: Icon(
|
||||
Icons.close,
|
||||
size: 15,
|
||||
color: StreamChatTheme.of(context)
|
||||
.primaryIconTheme
|
||||
.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||
),
|
||||
child: editMessageInputBuilder != null
|
||||
? editMessageInputBuilder(context, message)
|
||||
: MessageInput(
|
||||
editMessage: message,
|
||||
onMessageSent: (_) {
|
||||
FocusScope.of(context).unfocus();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
FlatButton _buildReplyButton(BuildContext context) {
|
||||
return FlatButton(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
'Start a thread',
|
||||
style: Theme.of(context).textTheme.headline5,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
if (onThreadTap != null) {
|
||||
onThreadTap(message);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -861,7 +861,6 @@ class MessageInputState extends State<MessageInput> {
|
||||
Message message;
|
||||
if (widget.editMessage != null) {
|
||||
message = widget.editMessage.copyWith(
|
||||
parentId: widget.parentMessage?.id,
|
||||
text: text,
|
||||
attachments: _getAttachments(attachments).toList(),
|
||||
mentionedUsers:
|
||||
|
||||
+152
-123
@@ -3,19 +3,46 @@ import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:jiffy/jiffy.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:stream_chat_flutter/src/message_widget.dart';
|
||||
import 'package:visibility_detector/visibility_detector.dart';
|
||||
|
||||
import '../stream_chat_flutter.dart';
|
||||
import 'date_divider.dart';
|
||||
import 'message_widget.dart';
|
||||
import 'stream_channel.dart';
|
||||
|
||||
typedef MessageBuilder = Widget Function(
|
||||
BuildContext, Message, List<Message>, int index);
|
||||
typedef ParentMessageBuilder = Widget Function(BuildContext, Message);
|
||||
BuildContext,
|
||||
MessageDetails,
|
||||
List<Message>,
|
||||
);
|
||||
typedef ParentMessageBuilder = Widget Function(
|
||||
BuildContext,
|
||||
Message,
|
||||
);
|
||||
typedef ThreadBuilder = Widget Function(BuildContext context, Message parent);
|
||||
typedef ThreadTapCallback = void Function(Message, Widget);
|
||||
|
||||
class MessageDetails {
|
||||
bool isMyMessage;
|
||||
bool isLastUser;
|
||||
bool isNextUser;
|
||||
Message message;
|
||||
int index;
|
||||
|
||||
MessageDetails(
|
||||
BuildContext context,
|
||||
this.message,
|
||||
List<Message> messages,
|
||||
this.index,
|
||||
) {
|
||||
isMyMessage = message.user.id == StreamChat.of(context).user.id;
|
||||
isLastUser = index + 1 < messages.length &&
|
||||
message.user.id == messages[index + 1]?.user?.id;
|
||||
isNextUser =
|
||||
index - 1 >= 0 && message.user.id == messages[index - 1]?.user?.id;
|
||||
}
|
||||
}
|
||||
|
||||
/// 
|
||||
/// 
|
||||
///
|
||||
@@ -65,15 +92,7 @@ class MessageListView extends StatefulWidget {
|
||||
this.parentMessage,
|
||||
this.threadBuilder,
|
||||
this.onThreadTap,
|
||||
this.showOtherMessageUsername = false,
|
||||
this.showVideoFullScreen = true,
|
||||
this.onMentionTap,
|
||||
this.onUserAvatarTap,
|
||||
this.onMessageActions,
|
||||
this.attachmentBuilders,
|
||||
this.dateDividerBuilder,
|
||||
this.showAvatar = true,
|
||||
this.editMessageInputBuilder,
|
||||
this.scrollPhysics = const AlwaysScrollableScrollPhysics(),
|
||||
}) : super(key: key);
|
||||
|
||||
@@ -93,38 +112,12 @@ class MessageListView extends StatefulWidget {
|
||||
/// Parent message in case of a thread
|
||||
final Message parentMessage;
|
||||
|
||||
/// If true show the other users username next to the timestamp of the message
|
||||
final bool showOtherMessageUsername;
|
||||
|
||||
/// True if the video player will allow fullscreen mode
|
||||
final bool showVideoFullScreen;
|
||||
|
||||
/// Function called on message mention tap
|
||||
final void Function(User) onMentionTap;
|
||||
|
||||
/// Function called on User Avatar tap
|
||||
final void Function(User) onUserAvatarTap;
|
||||
|
||||
/// Function called on message long press
|
||||
final Function(BuildContext, Message) onMessageActions;
|
||||
|
||||
/// Map that defines a builder for an attachment type
|
||||
final Map<String, AttachmentBuilder> attachmentBuilders;
|
||||
|
||||
/// Builder used to render date dividers
|
||||
final Widget Function(DateTime) dateDividerBuilder;
|
||||
|
||||
/// if true shows the user avatar
|
||||
final bool showAvatar;
|
||||
|
||||
|
||||
/// Builder used to build the message input to edit a message
|
||||
final Widget Function(BuildContext, Message) editMessageInputBuilder;
|
||||
|
||||
/// The ScrollPhysics used by the ListView
|
||||
final ScrollPhysics scrollPhysics;
|
||||
|
||||
|
||||
@override
|
||||
_MessageListViewState createState() => _MessageListViewState();
|
||||
}
|
||||
@@ -171,24 +164,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
MessageWidget(
|
||||
key: ValueKey<String>(
|
||||
'PARENT-MESSAGE-${widget.parentMessage.id}'),
|
||||
previousMessage: null,
|
||||
message: widget.parentMessage,
|
||||
nextMessage: null,
|
||||
onThreadTap: _onThreadTap,
|
||||
isParent: true,
|
||||
showVideoFullScreen: widget.showVideoFullScreen,
|
||||
showOtherMessageUsername:
|
||||
widget.showOtherMessageUsername,
|
||||
onMentionTap: widget.onMentionTap,
|
||||
onUserAvatarTap: widget.onUserAvatarTap,
|
||||
onMessageActions: widget.onMessageActions,
|
||||
attachmentBuilders: widget.attachmentBuilders,
|
||||
showAvatar: widget.showAvatar,
|
||||
editMessageInputBuilder: widget.editMessageInputBuilder,
|
||||
),
|
||||
buildParentMessage(widget.parentMessage),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: Container(
|
||||
@@ -212,50 +188,40 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
return _buildLoadingIndicator(streamChannel);
|
||||
}
|
||||
final message = _messages[i];
|
||||
|
||||
final previousMessage =
|
||||
i < _messages.length - 1 ? _messages[i + 1] : null;
|
||||
final nextMessage = i > 0 ? _messages[i - 1] : null;
|
||||
|
||||
Widget messageWidget;
|
||||
|
||||
if (i == 0) {
|
||||
messageWidget = _buildBottomMessage(
|
||||
streamChannel,
|
||||
previousMessage,
|
||||
message,
|
||||
context,
|
||||
message,
|
||||
_messages,
|
||||
streamChannel,
|
||||
);
|
||||
} else if (i == _messages.length - 1) {
|
||||
messageWidget = _buildTopMessage(
|
||||
message,
|
||||
nextMessage,
|
||||
streamChannel,
|
||||
context,
|
||||
message,
|
||||
_messages,
|
||||
streamChannel,
|
||||
);
|
||||
} else {
|
||||
if (widget.messageBuilder != null) {
|
||||
messageWidget = Builder(
|
||||
key: ValueKey<String>('MESSAGE-${message.id}'),
|
||||
builder: (_) =>
|
||||
widget.messageBuilder(context, message, _messages, i),
|
||||
builder: (_) => widget.messageBuilder(
|
||||
context,
|
||||
MessageDetails(
|
||||
context,
|
||||
message,
|
||||
_messages,
|
||||
i,
|
||||
),
|
||||
_messages),
|
||||
);
|
||||
} else {
|
||||
messageWidget = MessageWidget(
|
||||
key: ValueKey<String>('MESSAGE-${message.id}'),
|
||||
previousMessage: previousMessage,
|
||||
message: message,
|
||||
nextMessage: nextMessage,
|
||||
onThreadTap: _onThreadTap,
|
||||
showOtherMessageUsername: widget.showOtherMessageUsername,
|
||||
showVideoFullScreen: widget.showVideoFullScreen,
|
||||
onMentionTap: widget.onMentionTap,
|
||||
onUserAvatarTap: widget.onUserAvatarTap,
|
||||
onMessageActions: widget.onMessageActions,
|
||||
attachmentBuilders: widget.attachmentBuilders,
|
||||
showAvatar: widget.showAvatar,
|
||||
editMessageInputBuilder: widget.editMessageInputBuilder,
|
||||
);
|
||||
messageWidget = buildMessage(message, _messages, i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,38 +285,28 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
}
|
||||
|
||||
Widget _buildTopMessage(
|
||||
Message message,
|
||||
Message nextMessage,
|
||||
StreamChannelState streamChannelState,
|
||||
BuildContext context,
|
||||
Message message,
|
||||
List<Message> messages,
|
||||
StreamChannelState streamChannel,
|
||||
) {
|
||||
Widget messageWidget;
|
||||
if (widget.messageBuilder != null) {
|
||||
messageWidget = Builder(
|
||||
key: ValueKey<String>('MESSAGE-${message.id}'),
|
||||
key: ValueKey<String>('TOP-MESSAGE'),
|
||||
builder: (_) => widget.messageBuilder(
|
||||
context,
|
||||
message,
|
||||
MessageDetails(
|
||||
context,
|
||||
message,
|
||||
_messages,
|
||||
_messages.length - 1,
|
||||
),
|
||||
_messages,
|
||||
_messages.length - 1,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
messageWidget = MessageWidget(
|
||||
key: ValueKey<String>('MESSAGE-${message.id}'),
|
||||
previousMessage: null,
|
||||
message: message,
|
||||
nextMessage: nextMessage,
|
||||
onThreadTap: _onThreadTap,
|
||||
showVideoFullScreen: widget.showVideoFullScreen,
|
||||
showOtherMessageUsername: widget.showOtherMessageUsername,
|
||||
onMentionTap: widget.onMentionTap,
|
||||
onUserAvatarTap: widget.onUserAvatarTap,
|
||||
onMessageActions: widget.onMessageActions,
|
||||
attachmentBuilders: widget.attachmentBuilders,
|
||||
showAvatar: widget.showAvatar,
|
||||
editMessageInputBuilder: widget.editMessageInputBuilder,
|
||||
);
|
||||
messageWidget = buildMessage(message, messages, _messages.length - 1);
|
||||
}
|
||||
|
||||
return VisibilityDetector(
|
||||
@@ -359,7 +315,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
onVisibilityChanged: (visibility) {
|
||||
final topIsVisible = visibility.visibleBounds != Rect.zero;
|
||||
if (topIsVisible && !_topWasVisible) {
|
||||
streamChannelState.queryMessages();
|
||||
streamChannel.queryMessages();
|
||||
}
|
||||
_topWasVisible = topIsVisible;
|
||||
},
|
||||
@@ -367,33 +323,28 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
}
|
||||
|
||||
Widget _buildBottomMessage(
|
||||
StreamChannelState streamChannel,
|
||||
Message previousMessage,
|
||||
Message message,
|
||||
BuildContext context,
|
||||
Message message,
|
||||
List<Message> messages,
|
||||
StreamChannelState streamChannel,
|
||||
) {
|
||||
Widget messageWidget;
|
||||
if (widget.messageBuilder != null) {
|
||||
messageWidget = Builder(
|
||||
key: ValueKey<String>('MESSAGE-${message.id}'),
|
||||
builder: (_) => widget.messageBuilder(context, message, _messages, 0),
|
||||
key: ValueKey<String>('BOTTOM-MESSAGE'),
|
||||
builder: (_) => widget.messageBuilder(
|
||||
context,
|
||||
MessageDetails(
|
||||
context,
|
||||
message,
|
||||
_messages,
|
||||
0,
|
||||
),
|
||||
_messages,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
messageWidget = MessageWidget(
|
||||
key: ValueKey<String>('MESSAGE-${message.id}'),
|
||||
previousMessage: previousMessage,
|
||||
message: message,
|
||||
nextMessage: null,
|
||||
onThreadTap: _onThreadTap,
|
||||
showVideoFullScreen: widget.showVideoFullScreen,
|
||||
showOtherMessageUsername: widget.showOtherMessageUsername,
|
||||
onMentionTap: widget.onMentionTap,
|
||||
onUserAvatarTap: widget.onUserAvatarTap,
|
||||
onMessageActions: widget.onMessageActions,
|
||||
attachmentBuilders: widget.attachmentBuilders,
|
||||
showAvatar: widget.showAvatar,
|
||||
editMessageInputBuilder: widget.editMessageInputBuilder,
|
||||
);
|
||||
messageWidget = buildMessage(message, messages, 0);
|
||||
}
|
||||
|
||||
return VisibilityDetector(
|
||||
@@ -410,6 +361,84 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildParentMessage(
|
||||
Message message,
|
||||
) {
|
||||
final isMyMessage = message.user.id == StreamChat.of(context).user.id;
|
||||
|
||||
return MessageWidget(
|
||||
showReplyIndicator: false,
|
||||
message: message,
|
||||
reverse: isMyMessage,
|
||||
showUsername: !isMyMessage,
|
||||
padding: EdgeInsets.only(
|
||||
top: 8.0,
|
||||
left: 8.0,
|
||||
right: 8.0,
|
||||
bottom: 16.0,
|
||||
),
|
||||
showSendingIndicator: DisplayWidget.hide,
|
||||
onThreadTap: _onThreadTap,
|
||||
showEditMessage: false,
|
||||
showDeleteMessage: false,
|
||||
borderRadiusGeometry: BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
bottomLeft: Radius.circular(2),
|
||||
topRight: Radius.circular(16),
|
||||
bottomRight: Radius.circular(16),
|
||||
),
|
||||
borderSide: isMyMessage ? BorderSide.none : null,
|
||||
showUserAvatar: DisplayWidget.show,
|
||||
messageTheme: isMyMessage
|
||||
? StreamChatTheme.of(context).ownMessageTheme
|
||||
: StreamChatTheme.of(context).otherMessageTheme,
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildMessage(
|
||||
Message message,
|
||||
List<Message> messages,
|
||||
int index,
|
||||
) {
|
||||
final isMyMessage = message.user.id == StreamChat.of(context).user.id;
|
||||
final isLastUser = index + 1 < messages.length &&
|
||||
message.user.id == messages[index + 1]?.user?.id;
|
||||
final isNextUser =
|
||||
index - 1 >= 0 && message.user.id == messages[index - 1]?.user?.id;
|
||||
|
||||
return MessageWidget(
|
||||
message: message,
|
||||
reverse: isMyMessage,
|
||||
showReactions: !message.isDeleted,
|
||||
padding: EdgeInsets.only(
|
||||
left: 8.0,
|
||||
right: 8.0,
|
||||
bottom: index == 0 ? 30 : (isLastUser ? 5 : 10),
|
||||
),
|
||||
showUsername: !isMyMessage && !isNextUser,
|
||||
showSendingIndicator: isMyMessage &&
|
||||
(index == 0 || message.status != MessageSendingStatus.SENT)
|
||||
? DisplayWidget.show
|
||||
: DisplayWidget.hide,
|
||||
showTimestamp: !isNextUser,
|
||||
showEditMessage: isMyMessage,
|
||||
showDeleteMessage: isMyMessage,
|
||||
borderSide: isMyMessage ? BorderSide.none : null,
|
||||
onThreadTap: _onThreadTap,
|
||||
attachmentBorderRadiusGeometry: BorderRadius.circular(16),
|
||||
borderRadiusGeometry: BorderRadius.only(
|
||||
topLeft: Radius.circular(isLastUser ? 2 : 16),
|
||||
bottomLeft: Radius.circular(2),
|
||||
topRight: Radius.circular(16),
|
||||
bottomRight: Radius.circular(16),
|
||||
),
|
||||
showUserAvatar: isNextUser ? DisplayWidget.hide : DisplayWidget.show,
|
||||
messageTheme: isMyMessage
|
||||
? StreamChatTheme.of(context).ownMessageTheme
|
||||
: StreamChatTheme.of(context).otherMessageTheme,
|
||||
);
|
||||
}
|
||||
|
||||
StreamSubscription _streamListener;
|
||||
|
||||
@override
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
|
||||
import 'stream_chat_theme.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
class MessageText extends StatelessWidget {
|
||||
const MessageText({
|
||||
Key key,
|
||||
@required this.message,
|
||||
@required this.messageTheme,
|
||||
this.onMentionTap,
|
||||
}) : super(key: key);
|
||||
|
||||
final Message message;
|
||||
final void Function(User) onMentionTap;
|
||||
final MessageTheme messageTheme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final text = _replaceMentions(message.text);
|
||||
return MarkdownBody(
|
||||
data: text,
|
||||
onTapLink: (link) {
|
||||
if (link.startsWith('@')) {
|
||||
final mentionedUser = message.mentionedUsers.firstWhere(
|
||||
(u) => '@${u.name.replaceAll(' ', '')}' == link,
|
||||
orElse: () => null,
|
||||
);
|
||||
|
||||
if (onMentionTap != null) {
|
||||
onMentionTap(mentionedUser);
|
||||
} else {
|
||||
print('tap on ${mentionedUser.name}');
|
||||
}
|
||||
} else {
|
||||
launchURL(context, link);
|
||||
}
|
||||
},
|
||||
styleSheet: MarkdownStyleSheet.fromTheme(
|
||||
Theme.of(context).copyWith(
|
||||
textTheme: Theme.of(context).textTheme.apply(
|
||||
bodyColor: messageTheme.messageText.color,
|
||||
decoration: messageTheme.messageText.decoration,
|
||||
decorationColor: messageTheme.messageText.decorationColor,
|
||||
decorationStyle: messageTheme.messageText.decorationStyle,
|
||||
fontFamily: messageTheme.messageText.fontFamily,
|
||||
),
|
||||
),
|
||||
).copyWith(
|
||||
p: messageTheme.messageText,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _replaceMentions(String text) {
|
||||
message.mentionedUsers?.forEach((u) {
|
||||
text = text.replaceAll(
|
||||
'@${u.name}', '[@${u.name}](@${u.name.replaceAll(' ', '')})');
|
||||
});
|
||||
return text;
|
||||
}
|
||||
}
|
||||
+583
-853
File diff suppressed because it is too large
Load Diff
@@ -24,47 +24,50 @@ class ReactionPicker extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: reactionToEmoji.keys.map((reactionType) {
|
||||
final ownReactionIndex = message.ownReactions
|
||||
?.indexWhere((reaction) => reaction.type == reactionType) ??
|
||||
-1;
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
iconSize: size,
|
||||
icon: Text(
|
||||
reactionToEmoji[reactionType],
|
||||
style: TextStyle(
|
||||
fontSize: size - 10,
|
||||
return Container(
|
||||
color: Colors.black87,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: reactionToEmoji.keys.map((reactionType) {
|
||||
final ownReactionIndex = message.ownReactions
|
||||
?.indexWhere((reaction) => reaction.type == reactionType) ??
|
||||
-1;
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
iconSize: size,
|
||||
icon: Text(
|
||||
reactionToEmoji[reactionType],
|
||||
style: TextStyle(
|
||||
fontSize: size - 10,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
if (ownReactionIndex != -1) {
|
||||
removeReaction(
|
||||
context, message.ownReactions[ownReactionIndex]);
|
||||
} else {
|
||||
sendReaction(context, reactionType);
|
||||
}
|
||||
},
|
||||
),
|
||||
onPressed: () {
|
||||
if (ownReactionIndex != -1) {
|
||||
removeReaction(
|
||||
context, message.ownReactions[ownReactionIndex]);
|
||||
} else {
|
||||
sendReaction(context, reactionType);
|
||||
}
|
||||
},
|
||||
),
|
||||
ownReactionIndex != -1
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4.0),
|
||||
child: Text(
|
||||
message.ownReactions[ownReactionIndex].score.toString(),
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
)
|
||||
: SizedBox(),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
ownReactionIndex != -1
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4.0),
|
||||
child: Text(
|
||||
message.ownReactions[ownReactionIndex].score.toString(),
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
)
|
||||
: SizedBox(),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ class ReplyIndicator extends StatelessWidget {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: row,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -13,53 +13,38 @@ class SendingIndicator extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (message.status == MessageSendingStatus.SENT || message.status == null) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 1.0,
|
||||
),
|
||||
child: CircleAvatar(
|
||||
radius: 4,
|
||||
backgroundColor: StreamChatTheme.of(context).accentColor,
|
||||
child: Icon(
|
||||
Icons.done,
|
||||
color: Colors.white,
|
||||
size: 4,
|
||||
),
|
||||
return CircleAvatar(
|
||||
radius: 4,
|
||||
backgroundColor: StreamChatTheme.of(context).accentColor,
|
||||
child: Icon(
|
||||
Icons.done,
|
||||
color: Colors.white,
|
||||
size: 4,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (message.status == MessageSendingStatus.SENDING ||
|
||||
message.status == MessageSendingStatus.UPDATING) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 1.0,
|
||||
),
|
||||
child: CircleAvatar(
|
||||
radius: 4,
|
||||
backgroundColor: Colors.grey,
|
||||
child: Icon(
|
||||
Icons.access_time,
|
||||
size: 4,
|
||||
color: Colors.white,
|
||||
),
|
||||
return CircleAvatar(
|
||||
radius: 4,
|
||||
backgroundColor: Colors.grey,
|
||||
child: Icon(
|
||||
Icons.access_time,
|
||||
size: 4,
|
||||
color: Colors.white,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (message.status == MessageSendingStatus.FAILED ||
|
||||
message.status == MessageSendingStatus.FAILED_UPDATE ||
|
||||
message.status == MessageSendingStatus.FAILED_DELETE) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 1.0,
|
||||
),
|
||||
child: CircleAvatar(
|
||||
radius: 4,
|
||||
backgroundColor: Color(0xffd0021B).withOpacity(.1),
|
||||
child: Icon(
|
||||
Icons.error_outline,
|
||||
size: 4,
|
||||
color: Colors.white,
|
||||
),
|
||||
return CircleAvatar(
|
||||
radius: 4,
|
||||
backgroundColor: Color(0xffd0021B).withOpacity(.1),
|
||||
child: Icon(
|
||||
Icons.error_outline,
|
||||
size: 4,
|
||||
color: Colors.white,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user