From 7f696236c446b4038d2e06eae08318bbc9af9ac0 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 31 Dec 2020 13:05:32 +0530 Subject: [PATCH] [Message Widget] Add message error state Signed-off-by: Sahil Kumar --- lib/src/message_actions_modal.dart | 30 +++++- lib/src/message_widget.dart | 147 +++++++++++------------------ lib/src/sending_indicator.dart | 9 -- lib/src/stream_svg_icon.dart | 25 +++++ 4 files changed, 107 insertions(+), 104 deletions(-) diff --git a/lib/src/message_actions_modal.dart b/lib/src/message_actions_modal.dart index 8013a8b3..2d482c0e 100644 --- a/lib/src/message_actions_modal.dart +++ b/lib/src/message_actions_modal.dart @@ -22,6 +22,7 @@ class MessageActionsModal extends StatelessWidget { final bool showDeleteMessage; final bool showCopyMessage; final bool showEditMessage; + final bool showResendMessage; final bool showReply; final bool reverse; final ShapeBorder messageShape; @@ -37,6 +38,7 @@ class MessageActionsModal extends StatelessWidget { this.onThreadTap, this.showCopyMessage = true, this.showReply = true, + this.showResendMessage = true, this.showUserAvatar = DisplayWidget.show, this.editMessageInputBuilder, this.messageShape, @@ -159,6 +161,8 @@ class MessageActionsModal extends StatelessWidget { message.status == null) && message.parentId == null) _buildReplyButton(context), + if (showResendMessage) + _buildResendMessage(context), if (showEditMessage) _buildEditMessage(context), if (showDeleteMessage) @@ -183,9 +187,10 @@ class MessageActionsModal extends StatelessWidget { } Widget _buildDeleteButton(BuildContext context) { + final isDeleteFailed = message.status == MessageSendingStatus.FAILED_DELETE; return ListTile( title: Text( - 'Delete message', + isDeleteFailed ? 'Retry deleting message' : 'Delete message', style: Theme.of(context).textTheme.headline6.copyWith(color: Colors.red), ), @@ -234,6 +239,29 @@ class MessageActionsModal extends StatelessWidget { ); } + Widget _buildResendMessage(BuildContext context) { + final isUpdateFailed = message.status == MessageSendingStatus.FAILED_UPDATE; + return ListTile( + title: Text( + isUpdateFailed ? 'Resend edited message' : 'Resend', + style: Theme.of(context).textTheme.headline6, + ), + leading: StreamSvgIcon.circle_up( + color: StreamChatTheme.of(context).accentColor, + ), + onTap: () { + Navigator.pop(context); + final client = StreamChat.of(context).client; + final channel = StreamChannel.of(context).channel; + if (isUpdateFailed) { + client.updateMessage(message, channel.cid); + } else { + channel.sendMessage(message); + } + }, + ); + } + void _showEditBottomSheet(BuildContext context) { final channel = StreamChannel.of(context).channel; showModalBottomSheet( diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 9d81d679..ee165057 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -114,6 +114,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 readList; /// If true show the users username next to the timestamp of the message @@ -157,6 +160,7 @@ class MessageWidget extends StatefulWidget { this.editMessageInputBuilder, this.textBuilder, Map customAttachmentBuilders, + this.showResendMessage = true, this.readList, this.padding, this.textPadding = const EdgeInsets.symmetric( @@ -229,6 +233,16 @@ class _MessageWidgetState extends State { bool get showInChannel => widget.showInChannelIndicator && widget.message?.showInChannel == true; + 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; + @override Widget build(BuildContext context) { var leftPadding = widget.showUserAvatar != DisplayWidget.gone @@ -298,9 +312,7 @@ class _MessageWidgetState extends State { ) : EdgeInsets.zero, child: (widget.message.isDeleted && - widget.message.status != - MessageSendingStatus - .FAILED_DELETE) + !isFailedState) ? Transform( alignment: Alignment.center, transform: Matrix4.rotationY( @@ -377,6 +389,21 @@ class _MessageWidgetState extends State { ), ), ), + if (isFailedState) + Positioned( + left: widget.reverse ? -6 : null, + right: widget.reverse ? null : -6, + bottom: 0, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + child: StreamSvgIcon.error( + size: 20, + ), + ), + ), ], ), ), @@ -619,14 +646,19 @@ class _MessageWidgetState extends State { message: widget.message, editMessageInputBuilder: widget.editMessageInputBuilder, onThreadTap: widget.onThreadTap, - showCopyMessage: widget.message.text?.trim()?.isNotEmpty == true, + 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.showThreadReplyIndicator && widget.onThreadTap != null, + showReply: widget.showThreadReplyIndicator && + !isFailedState && + widget.onThreadTap != null, ), ); }); @@ -729,7 +761,6 @@ class _MessageWidgetState extends State { final attachmentShape = widget.attachmentShape ?? widget.shape ?? _getDefaultShape(context); return GestureDetector( - onTap: () => retryMessage(context), onLongPress: () => onLongPress(context), child: Material( color: _getBackgroundColor(), @@ -744,17 +775,7 @@ class _MessageWidgetState extends State { child: Transform( transform: Matrix4.rotationY(widget.reverse ? pi : 0), alignment: Alignment.center, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - getFailedMessageWidget( - context, - padding: const EdgeInsets.all(8.0), - ), - attachmentWidget, - ], - ), + child: attachmentWidget, ), ), ), @@ -809,55 +830,6 @@ class _MessageWidgetState extends State { ), ); - 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) { Widget child = Transform( transform: Matrix4.rotationY(widget.reverse ? pi : 0), @@ -867,26 +839,20 @@ class _MessageWidgetState extends State { 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) == @@ -896,7 +862,6 @@ class _MessageWidgetState extends State { ), ); return GestureDetector( - onTap: () => retryMessage(context), onLongPress: () => onLongPress(context), child: child, ); @@ -907,12 +872,6 @@ class _MessageWidgetState extends State { widget.message.text.characters.every((c) => Emoji.byChar(c) != null); Color _getBackgroundColor() { - 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) { diff --git a/lib/src/sending_indicator.dart b/lib/src/sending_indicator.dart index ce8915c3..6006f62d 100644 --- a/lib/src/sending_indicator.dart +++ b/lib/src/sending_indicator.dart @@ -35,15 +35,6 @@ class SendingIndicator extends StatelessWidget { size: 8, ); } - if (message.status == MessageSendingStatus.FAILED || - message.status == MessageSendingStatus.FAILED_UPDATE || - message.status == MessageSendingStatus.FAILED_DELETE) { - return Icon( - Icons.error_outline, - size: 8, - ); - } - return SizedBox(); } } diff --git a/lib/src/stream_svg_icon.dart b/lib/src/stream_svg_icon.dart index 98ce0f8e..c75e9327 100644 --- a/lib/src/stream_svg_icon.dart +++ b/lib/src/stream_svg_icon.dart @@ -757,4 +757,29 @@ class StreamSvgIcon extends StatelessWidget { height: size, ); } + + factory StreamSvgIcon.error({ + double size, + Color color, + }) { + return StreamSvgIcon( + assetName: 'Icon_error.svg', + color: color, + width: size, + height: size, + ); + } + + + factory StreamSvgIcon.circle_up({ + double size, + Color color, + }) { + return StreamSvgIcon( + assetName: 'Icon_circle_up.svg', + color: color, + width: size, + height: size, + ); + } }