Merge branch 'feature/new-ui' of github.com:GetStream/stream-chat-flutter into feat/messg-reply
Conflicts: example/ios/Flutter/.last_build_id example/lib/main.dart lib/src/file_attachment.dart lib/src/message_actions_modal.dart lib/src/message_input.dart lib/src/message_widget.dart
This commit is contained in:
+193
-223
@@ -120,6 +120,9 @@ class MessageWidget extends StatefulWidget {
|
||||
/// Used in [MessageReactionsModal] and [MessageActionsModal]
|
||||
final bool showReactionPickerIndicator;
|
||||
|
||||
/// If true the widget will show the resendMessage indicator
|
||||
final bool showResendMessage;
|
||||
|
||||
final List<Read> readList;
|
||||
|
||||
/// If true show the users username next to the timestamp of the message
|
||||
@@ -165,6 +168,7 @@ class MessageWidget extends StatefulWidget {
|
||||
this.editMessageInputBuilder,
|
||||
this.textBuilder,
|
||||
Map<String, AttachmentBuilder> customAttachmentBuilders,
|
||||
this.showResendMessage = true,
|
||||
this.readList,
|
||||
this.padding,
|
||||
this.textPadding = const EdgeInsets.symmetric(
|
||||
@@ -232,13 +236,29 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
bool get showTimeStamp =>
|
||||
widget.message.createdAt != null && widget.showTimestamp;
|
||||
|
||||
bool get showReadList => widget.readList?.isNotEmpty == true;
|
||||
bool get isMessageRead => widget.readList?.isNotEmpty == true;
|
||||
|
||||
bool get showInChannel =>
|
||||
widget.showInChannelIndicator && widget.message?.showInChannel == true;
|
||||
|
||||
bool get _hasReplyToMessage => widget.message?.replyToMessage != null;
|
||||
|
||||
bool get isSendFailed => widget.message.status == MessageSendingStatus.FAILED;
|
||||
|
||||
bool get isUpdateFailed =>
|
||||
widget.message.status == MessageSendingStatus.FAILED_UPDATE;
|
||||
|
||||
bool get isDeleteFailed =>
|
||||
widget.message.status == MessageSendingStatus.FAILED_DELETE;
|
||||
|
||||
bool get isFailedState => isSendFailed || isUpdateFailed || isDeleteFailed;
|
||||
|
||||
bool get showBottomRow =>
|
||||
showThreadReplyIndicator ||
|
||||
showUsername ||
|
||||
showTimeStamp ||
|
||||
showInChannel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var leftPadding = widget.showUserAvatar != DisplayWidget.gone
|
||||
@@ -273,6 +293,7 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: AlignmentDirectional.bottomStart,
|
||||
children: [
|
||||
Column(
|
||||
@@ -317,97 +338,90 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
: 0,
|
||||
)
|
||||
: EdgeInsets.zero,
|
||||
child:
|
||||
(widget.message.isDeleted &&
|
||||
widget.message.status !=
|
||||
MessageSendingStatus
|
||||
.FAILED_DELETE)
|
||||
? Transform(
|
||||
alignment: Alignment.center,
|
||||
transform: Matrix4.rotationY(
|
||||
widget.reverse ? pi : 0),
|
||||
child: DeletedMessage(
|
||||
reverse: widget.reverse,
|
||||
borderRadiusGeometry: widget
|
||||
.borderRadiusGeometry,
|
||||
borderSide:
|
||||
widget.borderSide,
|
||||
shape: widget.shape,
|
||||
messageTheme:
|
||||
widget.messageTheme,
|
||||
),
|
||||
)
|
||||
: GestureDetector(
|
||||
onTap: () =>
|
||||
retryMessage(context),
|
||||
onLongPress: () =>
|
||||
onLongPress(context),
|
||||
child: Material(
|
||||
clipBehavior:
|
||||
Clip.antiAlias,
|
||||
shape: widget.shape ??
|
||||
RoundedRectangleBorder(
|
||||
side: isOnlyEmoji &&
|
||||
!_hasReplyToMessage
|
||||
? BorderSide.none
|
||||
: 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: Padding(
|
||||
padding: EdgeInsets.all(
|
||||
hasFiles ? 2.0 : 0.0),
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment
|
||||
.start,
|
||||
mainAxisSize:
|
||||
MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
if (_hasReplyToMessage)
|
||||
ReplyToMessageWidget(
|
||||
message: widget
|
||||
.message
|
||||
.replyToMessage,
|
||||
messageTheme: isMyMessage
|
||||
child: (widget.message.isDeleted &&
|
||||
!isFailedState)
|
||||
? Transform(
|
||||
alignment: Alignment.center,
|
||||
transform: Matrix4.rotationY(
|
||||
widget.reverse ? pi : 0),
|
||||
child: DeletedMessage(
|
||||
reverse: widget.reverse,
|
||||
borderRadiusGeometry:
|
||||
widget.borderRadiusGeometry,
|
||||
borderSide: widget.borderSide,
|
||||
shape: widget.shape,
|
||||
messageTheme:
|
||||
widget.messageTheme,
|
||||
),
|
||||
)
|
||||
: GestureDetector(
|
||||
onLongPress: () =>
|
||||
onLongPress(context),
|
||||
child: Material(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: widget.shape ??
|
||||
RoundedRectangleBorder(
|
||||
side: isOnlyEmoji
|
||||
? BorderSide.none
|
||||
: widget.borderSide ??
|
||||
BorderSide(
|
||||
color: Theme.of(context)
|
||||
.brightness ==
|
||||
Brightness
|
||||
.dark
|
||||
? StreamChatTheme.of(
|
||||
context)
|
||||
.otherMessageTheme
|
||||
.colorTheme
|
||||
.white
|
||||
.withAlpha(
|
||||
24)
|
||||
: StreamChatTheme.of(
|
||||
context)
|
||||
.ownMessageTheme,
|
||||
reverse: widget
|
||||
.reverse,
|
||||
.colorTheme
|
||||
.black
|
||||
.withAlpha(
|
||||
24),
|
||||
),
|
||||
..._parseAttachments(
|
||||
context),
|
||||
if (widget
|
||||
.message.text
|
||||
.trim()
|
||||
.isNotEmpty &&
|
||||
!isGiphy)
|
||||
_buildTextBubble(
|
||||
context),
|
||||
],
|
||||
),
|
||||
),
|
||||
borderRadius: widget
|
||||
.borderRadiusGeometry ??
|
||||
BorderRadius.zero,
|
||||
),
|
||||
color: _getBackgroundColor(),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(
|
||||
hasFiles ? 2.0 : 0.0),
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
mainAxisSize:
|
||||
MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
if (_hasReplyToMessage)
|
||||
ReplyToMessageWidget(
|
||||
message: widget
|
||||
.message
|
||||
.replyToMessage,
|
||||
messageTheme: isMyMessage
|
||||
? StreamChatTheme.of(
|
||||
context)
|
||||
.otherMessageTheme
|
||||
: StreamChatTheme.of(
|
||||
context)
|
||||
.ownMessageTheme,
|
||||
reverse: widget
|
||||
.reverse,
|
||||
),
|
||||
..._parseAttachments(
|
||||
context),
|
||||
if (widget.message.text
|
||||
.trim()
|
||||
.isNotEmpty &&
|
||||
!isGiphy)
|
||||
_buildTextBubble(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
),),
|
||||
),
|
||||
if (widget.showReactionPickerIndicator)
|
||||
Positioned(
|
||||
@@ -432,18 +446,23 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
),
|
||||
],
|
||||
),
|
||||
if (showThreadReplyIndicator ||
|
||||
showUsername ||
|
||||
showTimeStamp ||
|
||||
showInChannel)
|
||||
SizedBox(height: 20.0),
|
||||
if (showBottomRow) SizedBox(height: 20.0),
|
||||
],
|
||||
),
|
||||
if (showThreadReplyIndicator ||
|
||||
showUsername ||
|
||||
showTimeStamp ||
|
||||
showInChannel)
|
||||
_buildBottomRow(leftPadding)
|
||||
if (showBottomRow) _buildBottomRow(leftPadding),
|
||||
if (isFailedState)
|
||||
Positioned(
|
||||
left: widget.reverse ? -3 : null,
|
||||
right: widget.reverse ? null : -9,
|
||||
bottom: showBottomRow ? 20 : 0,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: StreamSvgIcon.error(size: 20),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -463,16 +482,18 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
StreamSvgIcon.eye(
|
||||
color: Colors.black.withOpacity(0.5),
|
||||
color:
|
||||
StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5),
|
||||
size: 16.0,
|
||||
),
|
||||
SizedBox(width: 8.0),
|
||||
Text(
|
||||
'Only visible to you',
|
||||
style: TextStyle(
|
||||
color: Colors.black.withOpacity(0.5),
|
||||
fontSize: 12.0,
|
||||
),
|
||||
style: StreamChatTheme.of(context).textTheme.footnote.copyWith(
|
||||
color: StreamChatTheme.of(context)
|
||||
.colorTheme
|
||||
.black
|
||||
.withOpacity(0.5)),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -510,14 +531,14 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
|
||||
children.addAll([
|
||||
if (showSendingIndicator) _buildSendingIndicator(),
|
||||
if (showReadList)
|
||||
SizedBox.fromSize(
|
||||
size: Size((widget.readList.length * 10.0) + 10, 17),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 4.0),
|
||||
child: _buildReadIndicator(),
|
||||
),
|
||||
),
|
||||
// if (showReadList)
|
||||
// SizedBox.fromSize(
|
||||
// size: Size((widget.readList.length * 10.0) + 10, 17),
|
||||
// child: Padding(
|
||||
// padding: const EdgeInsets.only(left: 4.0),
|
||||
// child: _buildReadIndicator(),
|
||||
// ),
|
||||
// ),
|
||||
if (showThreadReplyIndicator)
|
||||
InkWell(
|
||||
onTap: widget.onThreadTap != null ? onThreadTap : null,
|
||||
@@ -551,6 +572,7 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
child: CustomPaint(
|
||||
size: const Size(16, 32),
|
||||
painter: _ThreadReplyPainter(
|
||||
context: context,
|
||||
color: widget.messageTheme.replyThreadColor,
|
||||
),
|
||||
),
|
||||
@@ -599,7 +621,7 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
bottom: 0,
|
||||
top: 0,
|
||||
child: Material(
|
||||
color: Colors.white,
|
||||
color: StreamChatTheme.of(context).colorTheme.white,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: CircleBorder(),
|
||||
child: Padding(
|
||||
@@ -669,15 +691,20 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
editMessageInputBuilder: widget.editMessageInputBuilder,
|
||||
onReplyTap: widget.onReplyTap,
|
||||
onThreadReplyTap: widget.onThreadTap,
|
||||
showCopyMessage: widget.message.text?.trim()?.isNotEmpty == true,
|
||||
showEditMessage: widget.showEditMessage &&
|
||||
showResendMessage:
|
||||
widget.showResendMessage && (isSendFailed || isUpdateFailed),
|
||||
showCopyMessage: !isFailedState &&
|
||||
widget.message.text?.trim()?.isNotEmpty == true, showEditMessage: widget.showEditMessage &&
|
||||
!isDeleteFailed &&
|
||||
widget.message.attachments
|
||||
?.any((element) => element.type == 'giphy') !=
|
||||
true,
|
||||
showReactions: widget.showReactions,
|
||||
showReply: widget.showReplyIndicator,
|
||||
showReply: widget.showReplyIndicator &&
|
||||
!isFailedState &&
|
||||
widget.onReplyTap != null,
|
||||
showThreadReply:
|
||||
widget.showThreadReplyIndicator && widget.onThreadTap != null,
|
||||
widget.showThreadReplyIndicator && widget.onThreadTap != null,
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -714,8 +741,8 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
widget.borderSide ??
|
||||
BorderSide(
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white.withAlpha(24)
|
||||
: Colors.black.withAlpha(24),
|
||||
? StreamChatTheme.of(context).colorTheme.white.withAlpha(24)
|
||||
: StreamChatTheme.of(context).colorTheme.black.withAlpha(24),
|
||||
),
|
||||
borderRadius: widget.attachmentBorderRadiusGeometry ??
|
||||
widget.borderRadiusGeometry ??
|
||||
@@ -780,32 +807,22 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
final attachmentShape =
|
||||
widget.attachmentShape ?? widget.shape ?? _getDefaultShape(context);
|
||||
return Material(
|
||||
color: _getBackgroundColor(),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: attachmentShape,
|
||||
child: Padding(
|
||||
padding: widget.attachmentPadding,
|
||||
child: Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: attachmentShape,
|
||||
type: MaterialType.transparency,
|
||||
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,
|
||||
],
|
||||
color: _getBackgroundColor(),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: attachmentShape,
|
||||
child: Padding(
|
||||
padding: widget.attachmentPadding,
|
||||
child: Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: attachmentShape,
|
||||
type: MaterialType.transparency,
|
||||
child: Transform(
|
||||
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
||||
alignment: Alignment.center,
|
||||
child: attachmentWidget,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -824,14 +841,27 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
}
|
||||
|
||||
Widget _buildSendingIndicator() {
|
||||
return Container(
|
||||
height: widget.messageTheme.createdAt.fontSize + 2,
|
||||
width: widget.messageTheme.createdAt.fontSize + 2,
|
||||
child: SendingIndicator(
|
||||
message: widget.message,
|
||||
allRead: widget.allRead,
|
||||
),
|
||||
final style = widget.messageTheme.createdAt;
|
||||
Widget child = SendingIndicator(
|
||||
message: widget.message,
|
||||
isMessageRead: isMessageRead,
|
||||
size: style.fontSize,
|
||||
);
|
||||
if (isMessageRead) {
|
||||
child = Row(
|
||||
children: [
|
||||
Text(
|
||||
widget.readList.length.toString(),
|
||||
style: style.copyWith(
|
||||
color: StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 2),
|
||||
child,
|
||||
],
|
||||
);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
Widget _buildUserAvatar() => Transform(
|
||||
@@ -856,55 +886,6 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
),
|
||||
);
|
||||
|
||||
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 Transform(
|
||||
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
||||
@@ -914,26 +895,20 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
children: [
|
||||
Padding(
|
||||
padding: widget.textPadding,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
getFailedMessageWidget(context),
|
||||
widget.textBuilder != null
|
||||
? widget.textBuilder(context, widget.message)
|
||||
: MessageText(
|
||||
onLinkTap: widget.onLinkTap,
|
||||
message: widget.message,
|
||||
onMentionTap: widget.onMentionTap,
|
||||
messageTheme: isOnlyEmoji
|
||||
? widget.messageTheme.copyWith(
|
||||
messageText:
|
||||
widget.messageTheme.messageText.copyWith(
|
||||
fontSize: 40,
|
||||
))
|
||||
: widget.messageTheme,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: widget.textBuilder != null
|
||||
? widget.textBuilder(context, widget.message)
|
||||
: MessageText(
|
||||
onLinkTap: widget.onLinkTap,
|
||||
message: widget.message,
|
||||
onMentionTap: widget.onMentionTap,
|
||||
messageTheme: isOnlyEmoji
|
||||
? widget.messageTheme.copyWith(
|
||||
messageText:
|
||||
widget.messageTheme.messageText.copyWith(
|
||||
fontSize: 40,
|
||||
))
|
||||
: widget.messageTheme,
|
||||
),
|
||||
),
|
||||
if (widget.message.attachments
|
||||
?.any((element) => element.ogScrapeUrl != null) ==
|
||||
@@ -954,16 +929,10 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
return widget.messageTheme.messageBackgroundColor;
|
||||
}
|
||||
|
||||
if ((widget.message.status == MessageSendingStatus.FAILED ||
|
||||
widget.message.status == MessageSendingStatus.FAILED_UPDATE ||
|
||||
widget.message.status == MessageSendingStatus.FAILED_DELETE)) {
|
||||
return Color(0xffd0021B).withOpacity(.1);
|
||||
}
|
||||
|
||||
if (widget.message.attachments
|
||||
?.any((element) => element.ogScrapeUrl != null) ==
|
||||
true) {
|
||||
return Color(0xFFE9F2FF);
|
||||
return StreamChatTheme.of(context).colorTheme.blueAlice;
|
||||
}
|
||||
|
||||
if (isOnlyEmoji) {
|
||||
@@ -1003,13 +972,14 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
|
||||
class _ThreadReplyPainter extends CustomPainter {
|
||||
final Color color;
|
||||
final BuildContext context;
|
||||
|
||||
const _ThreadReplyPainter({@required this.color});
|
||||
const _ThreadReplyPainter({this.context, @required this.color});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()
|
||||
..color = color ?? Color(0XFFDBDBDB)
|
||||
..color = color ?? StreamChatTheme.of(context).colorTheme.greyGainsboro
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1
|
||||
..strokeCap = StrokeCap.round;
|
||||
|
||||
Reference in New Issue
Block a user