791 lines
26 KiB
Dart
791 lines
26 KiB
Dart
import 'dart:math';
|
|
import 'dart:ui';
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter_portal/flutter_portal.dart';
|
|
import 'package:jiffy/jiffy.dart';
|
|
import 'package:stream_chat_flutter/src/message_actions_modal.dart';
|
|
import 'package:stream_chat_flutter/src/message_reactions_modal.dart';
|
|
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
|
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
|
|
|
import 'image_group.dart';
|
|
import 'message_text.dart';
|
|
|
|
typedef AttachmentBuilder = Widget Function(BuildContext, Message, Attachment);
|
|
|
|
/// The display behaviour of a widget
|
|
enum DisplayWidget {
|
|
/// Hides the widget replacing its space with a spacer
|
|
hide,
|
|
|
|
/// Hides the widget not replacing its space
|
|
gone,
|
|
|
|
/// Shows the widget normally
|
|
show,
|
|
}
|
|
|
|
/// 
|
|
/// 
|
|
///
|
|
/// It shows a message with reactions, replies and user avatar.
|
|
///
|
|
/// Usually you don't use this widget as it's the default message widget used by [MessageListView].
|
|
///
|
|
/// The widget components render the ui based on the first ancestor of type [StreamChatTheme].
|
|
/// Modify it to change the widget appearance.
|
|
class MessageWidget extends StatefulWidget {
|
|
/// Function called on mention tap
|
|
final void Function(User) onMentionTap;
|
|
|
|
/// The function called when tapping on replies
|
|
final void Function(Message) onThreadTap;
|
|
final Widget Function(BuildContext, Message) editMessageInputBuilder;
|
|
final Widget Function(BuildContext, Message) textBuilder;
|
|
|
|
/// Function called on long press
|
|
final void Function(BuildContext, Message) onMessageActions;
|
|
|
|
/// The message
|
|
final Message message;
|
|
|
|
/// The message theme
|
|
final MessageTheme messageTheme;
|
|
|
|
/// If true the widget will be mirrored
|
|
final bool reverse;
|
|
|
|
/// The shape of the message text
|
|
final ShapeBorder shape;
|
|
|
|
/// The shape of an attachment
|
|
final ShapeBorder attachmentShape;
|
|
|
|
/// The borderside of the message text
|
|
final BorderSide borderSide;
|
|
|
|
/// The borderside of an attachment
|
|
final BorderSide attachmentBorderSide;
|
|
|
|
/// The border radius of the message text
|
|
final BorderRadiusGeometry borderRadiusGeometry;
|
|
|
|
/// The border radius of an attachment
|
|
final BorderRadiusGeometry attachmentBorderRadiusGeometry;
|
|
|
|
/// The padding of the widget
|
|
final EdgeInsetsGeometry padding;
|
|
|
|
/// The internal padding of the message text
|
|
final EdgeInsetsGeometry textPadding;
|
|
|
|
/// The internal padding of an attachment
|
|
final EdgeInsetsGeometry attachmentPadding;
|
|
|
|
/// It controls the display behaviour of the user avatar
|
|
final DisplayWidget showUserAvatar;
|
|
|
|
/// It controls the display behaviour of the sending indicator
|
|
final DisplayWidget showSendingIndicator;
|
|
|
|
/// If true the widget will show the reactions
|
|
final bool showReactions;
|
|
|
|
final bool allRead;
|
|
|
|
/// If true the widget will show the reply indicator
|
|
final bool showReplyIndicator;
|
|
|
|
/// The function called when tapping on UserAvatar
|
|
final void Function(User) onUserAvatarTap;
|
|
|
|
/// The function called when tapping on a link
|
|
final void Function(String) onLinkTap;
|
|
|
|
final List<Read> readList;
|
|
|
|
/// If true show the users username next to the timestamp of the message
|
|
final bool showUsername;
|
|
final bool showTimestamp;
|
|
final bool showDeleteMessage;
|
|
final bool showEditMessage;
|
|
final Map<String, AttachmentBuilder> attachmentBuilders;
|
|
|
|
MessageWidget({
|
|
Key key,
|
|
@required this.message,
|
|
@required this.messageTheme,
|
|
this.reverse = false,
|
|
this.shape,
|
|
this.attachmentShape,
|
|
this.borderSide,
|
|
this.attachmentBorderSide,
|
|
this.borderRadiusGeometry,
|
|
this.attachmentBorderRadiusGeometry,
|
|
this.onMentionTap,
|
|
this.showUserAvatar = DisplayWidget.show,
|
|
this.showSendingIndicator = DisplayWidget.show,
|
|
this.showReplyIndicator = true,
|
|
this.onThreadTap,
|
|
this.showUsername = true,
|
|
this.showTimestamp = true,
|
|
this.showReactions = true,
|
|
this.showDeleteMessage = true,
|
|
this.showEditMessage = true,
|
|
this.onUserAvatarTap,
|
|
this.onLinkTap,
|
|
this.onMessageActions,
|
|
this.editMessageInputBuilder,
|
|
this.textBuilder,
|
|
Map<String, AttachmentBuilder> customAttachmentBuilders,
|
|
this.readList,
|
|
this.padding,
|
|
this.textPadding = const EdgeInsets.symmetric(
|
|
horizontal: 16.0,
|
|
vertical: 8.0,
|
|
),
|
|
this.attachmentPadding = EdgeInsets.zero,
|
|
this.allRead = false,
|
|
}) : attachmentBuilders = {
|
|
'image': (context, message, attachment) {
|
|
return ImageAttachment(
|
|
attachment: attachment,
|
|
message: message,
|
|
messageTheme: messageTheme,
|
|
size: Size(
|
|
MediaQuery.of(context).size.width * 0.8,
|
|
MediaQuery.of(context).size.height * 0.3,
|
|
),
|
|
);
|
|
},
|
|
'video': (context, message, attachment) {
|
|
return VideoAttachment(
|
|
attachment: attachment,
|
|
messageTheme: messageTheme,
|
|
size: Size(
|
|
MediaQuery.of(context).size.width * 0.8,
|
|
MediaQuery.of(context).size.height * 0.3,
|
|
),
|
|
);
|
|
},
|
|
'giphy': (context, message, attachment) {
|
|
return GiphyAttachment(
|
|
attachment: attachment,
|
|
messageTheme: messageTheme,
|
|
message: message,
|
|
size: Size(
|
|
MediaQuery.of(context).size.width * 0.8,
|
|
MediaQuery.of(context).size.height * 0.3,
|
|
),
|
|
);
|
|
},
|
|
'file': (context, message, attachment) {
|
|
return FileAttachment(
|
|
attachment: attachment,
|
|
size: Size(
|
|
MediaQuery.of(context).size.width * 0.8,
|
|
MediaQuery.of(context).size.height * 0.3,
|
|
),
|
|
);
|
|
},
|
|
}..addAll(customAttachmentBuilders ?? {}),
|
|
super(key: key);
|
|
|
|
@override
|
|
_MessageWidgetState createState() => _MessageWidgetState();
|
|
}
|
|
|
|
class _MessageWidgetState extends State<MessageWidget> {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
var leftPadding = widget.showUserAvatar != DisplayWidget.gone
|
|
? widget.messageTheme.avatarTheme.constraints.maxWidth + 16.0
|
|
: 6.0;
|
|
return Portal(
|
|
child: Padding(
|
|
padding: widget.padding ?? EdgeInsets.all(8),
|
|
child: Transform(
|
|
alignment: Alignment.center,
|
|
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
|
child: FractionallySizedBox(
|
|
alignment: Alignment.centerLeft,
|
|
widthFactor: 0.75,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
if (widget.showUserAvatar == DisplayWidget.show)
|
|
_buildUserAvatar(),
|
|
SizedBox(
|
|
width: 6,
|
|
),
|
|
if (widget.showUserAvatar == DisplayWidget.hide)
|
|
SizedBox(
|
|
width: widget.messageTheme.avatarTheme.constraints
|
|
.maxWidth +
|
|
8,
|
|
),
|
|
Flexible(
|
|
child: Padding(
|
|
padding: widget.showReactions
|
|
? EdgeInsets.only(
|
|
top: widget.message.reactionCounts
|
|
?.isNotEmpty ==
|
|
true
|
|
? 12
|
|
: 0,
|
|
)
|
|
: EdgeInsets.zero,
|
|
child: PortalEntry(
|
|
portalAnchor: Alignment(-0.81, 0),
|
|
childAnchor: Alignment.topRight,
|
|
portal: _buildReactionIndicator(context),
|
|
child: (widget.message.isDeleted &&
|
|
widget.message.status !=
|
|
MessageSendingStatus.FAILED_DELETE)
|
|
? Transform(
|
|
alignment: Alignment.center,
|
|
transform: Matrix4.rotationY(
|
|
widget.reverse ? pi : 0),
|
|
child: DeletedMessage(
|
|
messageTheme: widget.messageTheme,
|
|
),
|
|
)
|
|
: Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
..._parseAttachments(context),
|
|
if (widget.message.text
|
|
.trim()
|
|
.isNotEmpty)
|
|
_buildTextBubble(context),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
if (widget.showReplyIndicator &&
|
|
widget.message.replyCount > 0)
|
|
_buildReplyIndicator(leftPadding),
|
|
],
|
|
),
|
|
if ((widget.message.createdAt != null &&
|
|
widget.showTimestamp) ||
|
|
widget.showUsername ||
|
|
widget.readList?.isNotEmpty == true)
|
|
_buildBottomRow(leftPadding),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Padding _buildBottomRow(double leftPadding) {
|
|
return Padding(
|
|
padding: EdgeInsets.only(
|
|
left: leftPadding,
|
|
top: 2,
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
Transform(
|
|
alignment: Alignment.center,
|
|
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
|
child: RichText(
|
|
text: TextSpan(
|
|
style: widget.messageTheme.createdAt,
|
|
children: <TextSpan>[
|
|
if (widget.showUsername)
|
|
TextSpan(
|
|
text: widget.message.user.name,
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
color: widget.messageTheme.createdAt.color
|
|
.withOpacity(1)),
|
|
),
|
|
if (widget.message.createdAt != null && widget.showTimestamp)
|
|
TextSpan(
|
|
text: Jiffy(widget.message.createdAt.toLocal())
|
|
.format(' HH:mm'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
if (widget.showSendingIndicator == DisplayWidget.show)
|
|
_buildSendingIndicator(),
|
|
if (widget.readList?.isNotEmpty == true)
|
|
SizedBox.fromSize(
|
|
size: Size((widget.readList.length * 10.0) + 10, 17),
|
|
child: Transform(
|
|
alignment: Alignment.center,
|
|
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(left: 4.0),
|
|
child: _buildReadIndicator(),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildReadIndicator() {
|
|
var padding = 0.0;
|
|
return Stack(
|
|
children: widget.readList.map((e) {
|
|
padding += 10.0;
|
|
return Positioned(
|
|
left: padding - 10,
|
|
bottom: 0,
|
|
top: 0,
|
|
child: Material(
|
|
color: Colors.white,
|
|
shape: CircleBorder(),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(1.0),
|
|
child: UserAvatar(
|
|
user: e.user,
|
|
constraints: BoxConstraints.loose(Size.fromRadius(16)),
|
|
showOnlineStatus: false,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}).toList(),
|
|
);
|
|
}
|
|
|
|
Widget _buildReactionIndicator(BuildContext context) {
|
|
final otherReactions = widget.message.latestReactions
|
|
?.where(
|
|
(element) => element.user.id != StreamChat.of(context).user.id)
|
|
?.toList() ??
|
|
[];
|
|
|
|
var rowChildren = [
|
|
if (widget.message.ownReactions?.isNotEmpty == true) ...[
|
|
Transform.translate(
|
|
offset: Offset(
|
|
widget.reverse ? -6 : 0,
|
|
0,
|
|
),
|
|
child: ReactionBubble(
|
|
key: ValueKey('${widget.message.id}.own'),
|
|
reverse: widget.reverse,
|
|
backgroundColor: widget.messageTheme.ownReactionsBackgroundColor,
|
|
borderColor: widget.messageTheme.ownReactionsBorderColor,
|
|
reactions: widget.message.ownReactions,
|
|
),
|
|
),
|
|
if (otherReactions.isEmpty)
|
|
Container(
|
|
width: 18,
|
|
),
|
|
],
|
|
if (otherReactions?.isNotEmpty == true) ...[
|
|
if (widget.message.ownReactions?.isEmpty == true)
|
|
Container(
|
|
width: 18,
|
|
),
|
|
Transform.translate(
|
|
offset: Offset(
|
|
widget.reverse ? 0 : -6,
|
|
0,
|
|
),
|
|
child: ReactionBubble(
|
|
key: ValueKey('${widget.message.id}.other'),
|
|
reactions: otherReactions,
|
|
reverse: widget.reverse,
|
|
flipTail: true,
|
|
backgroundColor: widget.messageTheme.otherReactionsBackgroundColor,
|
|
borderColor: widget.messageTheme.otherReactionsBorderColor,
|
|
),
|
|
),
|
|
]
|
|
];
|
|
|
|
if (widget.reverse) {
|
|
rowChildren = rowChildren.reversed.toList();
|
|
}
|
|
return AnimatedSwitcher(
|
|
duration: Duration(milliseconds: 300),
|
|
child: (widget.showReactions &&
|
|
(widget.message.reactionCounts?.isNotEmpty == true ||
|
|
otherReactions.isNotEmpty) &&
|
|
!widget.message.isDeleted)
|
|
? Container(
|
|
child: GestureDetector(
|
|
onTap: () => _showMessageReactionsModalBottomSheet(context),
|
|
child: FractionallySizedBox(
|
|
widthFactor: 0.5,
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: rowChildren,
|
|
),
|
|
),
|
|
),
|
|
)
|
|
: SizedBox(),
|
|
);
|
|
}
|
|
|
|
void _showMessageActionModalBottomSheet(BuildContext context) {
|
|
final channel = StreamChannel.of(context).channel;
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) {
|
|
return StreamChannel(
|
|
channel: channel,
|
|
child: MessageActionsModal(
|
|
messageTheme: widget.messageTheme,
|
|
messageShape: widget.shape ?? _getDefaultShape(context),
|
|
reverse: widget.reverse,
|
|
showDeleteMessage: widget.showDeleteMessage,
|
|
message: widget.message,
|
|
editMessageInputBuilder: widget.editMessageInputBuilder,
|
|
onThreadTap: widget.onThreadTap,
|
|
showEditMessage: widget.showEditMessage,
|
|
showReactions: widget.showReactions,
|
|
showReply:
|
|
widget.showReplyIndicator && widget.onThreadTap != null,
|
|
),
|
|
);
|
|
});
|
|
}
|
|
|
|
void _showMessageReactionsModalBottomSheet(BuildContext context) {
|
|
final channel = StreamChannel.of(context).channel;
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) {
|
|
return StreamChannel(
|
|
channel: channel,
|
|
child: MessageReactionsModal(
|
|
onUserAvatarTap: widget.onUserAvatarTap,
|
|
messageTheme: widget.messageTheme,
|
|
messageShape: widget.shape ?? _getDefaultShape(context),
|
|
reverse: widget.reverse,
|
|
message: widget.message,
|
|
editMessageInputBuilder: widget.editMessageInputBuilder,
|
|
onThreadTap: widget.onThreadTap,
|
|
showReactions: widget.showReactions,
|
|
),
|
|
);
|
|
});
|
|
}
|
|
|
|
ShapeBorder _getDefaultShape(BuildContext context) {
|
|
return RoundedRectangleBorder(
|
|
side: widget.attachmentBorderSide ??
|
|
widget.borderSide ??
|
|
BorderSide(
|
|
color: Theme.of(context).brightness == Brightness.dark
|
|
? Colors.white.withAlpha(24)
|
|
: Colors.black.withAlpha(24),
|
|
),
|
|
borderRadius: widget.attachmentBorderRadiusGeometry ??
|
|
widget.borderRadiusGeometry ??
|
|
BorderRadius.zero,
|
|
);
|
|
}
|
|
|
|
List<Widget> _parseAttachments(BuildContext context) {
|
|
final images = widget.message.attachments
|
|
?.where((element) => element.type == 'image')
|
|
?.toList() ??
|
|
[];
|
|
|
|
if (images.length > 1) {
|
|
return [
|
|
wrapAttachmentWidget(
|
|
context,
|
|
Material(
|
|
color: widget.messageTheme.messageBackgroundColor,
|
|
child: ImageGroup(
|
|
size: Size(
|
|
MediaQuery.of(context).size.width * 0.8,
|
|
MediaQuery.of(context).size.height * 0.3,
|
|
),
|
|
images: images,
|
|
message: widget.message,
|
|
),
|
|
),
|
|
),
|
|
];
|
|
}
|
|
|
|
return widget.message.attachments?.map((attachment) {
|
|
final attachmentBuilder = widget.attachmentBuilders[attachment.type];
|
|
|
|
if (attachmentBuilder == null) {
|
|
return SizedBox();
|
|
}
|
|
|
|
final attachmentWidget = attachmentBuilder(
|
|
context,
|
|
widget.message,
|
|
attachment,
|
|
);
|
|
return wrapAttachmentWidget(context, attachmentWidget);
|
|
})?.toList() ??
|
|
[];
|
|
}
|
|
|
|
Padding wrapAttachmentWidget(BuildContext context, Widget attachmentWidget) {
|
|
return Padding(
|
|
padding: EdgeInsets.only(
|
|
bottom: 4,
|
|
),
|
|
child: GestureDetector(
|
|
onTap: () => retryMessage(context),
|
|
onLongPress: () => onLongPress(context),
|
|
child: Material(
|
|
color: _getBackgroundColor(),
|
|
clipBehavior: Clip.hardEdge,
|
|
shape: widget.attachmentShape ??
|
|
widget.shape ??
|
|
_getDefaultShape(context),
|
|
child: Padding(
|
|
padding: widget.attachmentPadding,
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(6),
|
|
child: Transform(
|
|
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
|
alignment: Alignment.center,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: <Widget>[
|
|
getFailedMessageWidget(
|
|
context,
|
|
padding: const EdgeInsets.all(8.0),
|
|
),
|
|
attachmentWidget,
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void onLongPress(BuildContext context) {
|
|
if (widget.message.isEphemeral ||
|
|
widget.message.status == MessageSendingStatus.SENDING) {
|
|
return;
|
|
}
|
|
|
|
if (widget.onMessageActions != null) {
|
|
widget.onMessageActions(context, widget.message);
|
|
} else {
|
|
_showMessageActionModalBottomSheet(context);
|
|
}
|
|
return;
|
|
}
|
|
|
|
Widget _buildReplyIndicator(double leftPadding) {
|
|
return Padding(
|
|
padding: EdgeInsets.only(
|
|
left: leftPadding,
|
|
),
|
|
child: Transform(
|
|
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
|
alignment: Alignment.center,
|
|
child: ReplyIndicator(
|
|
message: widget.message,
|
|
reversed: widget.reverse,
|
|
messageTheme: widget.messageTheme,
|
|
onTap: widget.onThreadTap != null
|
|
? () {
|
|
widget.onThreadTap(widget.message);
|
|
}
|
|
: null,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSendingIndicator() {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(right: 4.0),
|
|
child: Transform(
|
|
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
|
alignment: Alignment.center,
|
|
child: SendingIndicator(
|
|
message: widget.message,
|
|
allRead: widget.allRead,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildUserAvatar() => Transform(
|
|
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
|
alignment: Alignment.center,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
|
child: Transform.translate(
|
|
offset: Offset(
|
|
0, widget.messageTheme.avatarTheme.constraints.maxHeight / 2),
|
|
child: UserAvatar(
|
|
user: widget.message.user,
|
|
onTap: widget.onUserAvatarTap,
|
|
constraints: widget.messageTheme.avatarTheme.constraints,
|
|
showOnlineStatus: false,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
Widget getFailedMessageWidget(
|
|
BuildContext context, {
|
|
EdgeInsetsGeometry padding,
|
|
}) {
|
|
Widget failedWidget;
|
|
if (widget.message.status == MessageSendingStatus.FAILED) {
|
|
failedWidget = Text(
|
|
'MESSAGE FAILED · CLICK TO TRY AGAIN',
|
|
style: widget.messageTheme.messageText.copyWith(
|
|
color: Theme.of(context).brightness == Brightness.dark
|
|
? Colors.white.withOpacity(.5)
|
|
: Colors.black.withOpacity(.5),
|
|
fontSize: 11,
|
|
),
|
|
);
|
|
}
|
|
if (widget.message.status == MessageSendingStatus.FAILED_UPDATE) {
|
|
failedWidget = Text(
|
|
'MESSAGE UPDATE FAILED · CLICK TO TRY AGAIN',
|
|
style: widget.messageTheme.messageText.copyWith(
|
|
color: Theme.of(context).brightness == Brightness.dark
|
|
? Colors.white.withOpacity(.5)
|
|
: Colors.black.withOpacity(.5),
|
|
fontSize: 11,
|
|
),
|
|
);
|
|
}
|
|
if (widget.message.status == MessageSendingStatus.FAILED_DELETE) {
|
|
failedWidget = Text(
|
|
'MESSAGE DELETE FAILED · CLICK TO TRY AGAIN',
|
|
style: widget.messageTheme.messageText.copyWith(
|
|
color: Theme.of(context).brightness == Brightness.dark
|
|
? Colors.white.withOpacity(.5)
|
|
: Colors.black.withOpacity(.5),
|
|
fontSize: 11,
|
|
),
|
|
);
|
|
}
|
|
|
|
if (failedWidget != null) {
|
|
return Padding(
|
|
padding: padding ?? EdgeInsets.zero,
|
|
child: failedWidget,
|
|
);
|
|
}
|
|
|
|
return SizedBox();
|
|
}
|
|
|
|
Widget _buildTextBubble(BuildContext context) {
|
|
return GestureDetector(
|
|
onTap: () => retryMessage(context),
|
|
onLongPress: () => onLongPress(context),
|
|
child: Material(
|
|
shape: widget.shape ??
|
|
RoundedRectangleBorder(
|
|
side: widget.borderSide ??
|
|
BorderSide(
|
|
color: Theme.of(context).brightness == Brightness.dark
|
|
? Colors.white.withAlpha(24)
|
|
: Colors.black.withAlpha(24),
|
|
),
|
|
borderRadius: widget.borderRadiusGeometry ?? BorderRadius.zero,
|
|
),
|
|
color: _getBackgroundColor(),
|
|
child: Transform(
|
|
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
|
alignment: Alignment.center,
|
|
child: Padding(
|
|
padding: widget.textPadding,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
getFailedMessageWidget(context),
|
|
_buildText(context),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Color _getBackgroundColor() {
|
|
return (widget.message.status == MessageSendingStatus.FAILED ||
|
|
widget.message.status == MessageSendingStatus.FAILED_UPDATE ||
|
|
widget.message.status == MessageSendingStatus.FAILED_DELETE)
|
|
? Color(0xffd0021B).withOpacity(.1)
|
|
: widget.messageTheme.messageBackgroundColor;
|
|
}
|
|
|
|
void retryMessage(BuildContext context) {
|
|
final channel = StreamChannel.of(context).channel;
|
|
if (widget.message.status == MessageSendingStatus.FAILED) {
|
|
channel.sendMessage(widget.message);
|
|
return;
|
|
}
|
|
if (widget.message.status == MessageSendingStatus.FAILED_UPDATE) {
|
|
StreamChat.of(context).client.updateMessage(
|
|
widget.message,
|
|
channel.cid,
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (widget.message.status == MessageSendingStatus.FAILED_DELETE) {
|
|
StreamChat.of(context).client.deleteMessage(
|
|
widget.message,
|
|
channel.cid,
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
Widget _buildText(BuildContext context) {
|
|
return widget.textBuilder != null
|
|
? widget.textBuilder(context, widget.message)
|
|
: MessageText(
|
|
onLinkTap: widget.onLinkTap,
|
|
message: widget.message,
|
|
onMentionTap: widget.onMentionTap,
|
|
messageTheme: widget.messageTheme,
|
|
);
|
|
}
|
|
}
|