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:
Sahil Kumar
2020-12-31 18:13:01 +05:30
53 changed files with 1863 additions and 1125 deletions
+193 -223
View File
@@ -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;