Merge pull request #189 from GetStream/messg-error-state

Message error state
This commit is contained in:
Salvatore Giordano
2020-12-31 11:52:33 +01:00
committed by GitHub
7 changed files with 169 additions and 151 deletions
+5 -1
View File
@@ -122,7 +122,11 @@ class ChannelPreview extends StatelessWidget {
padding: const EdgeInsets.only(right: 4.0),
child: SendingIndicator(
message: channel.state.lastMessage,
allRead: channel.state.read
size: StreamChatTheme.of(context)
.channelPreviewTheme
.lastMessageAt
.fontSize,
isMessageRead: channel.state.read
.where((element) => element.lastRead
.isAfter(channel
.state.lastMessage.createdAt))
+36 -6
View File
@@ -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,
@@ -117,7 +119,10 @@ class MessageActionsModal extends StatelessWidget {
showUserAvatar: showUserAvatar,
showTimestamp: false,
translateUserAvatar: false,
showReactionPickerIndicator: true,
showReactionPickerIndicator: showReactions &&
(message.status ==
MessageSendingStatus.SENT ||
message.status == null),
showInChannelIndicator: false,
showSendingIndicator: DisplayWidget.gone,
shape: messageShape,
@@ -125,9 +130,7 @@ class MessageActionsModal extends StatelessWidget {
),
);
}),
SizedBox(
height: 8,
),
SizedBox(height: 8),
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: Duration(milliseconds: 300),
@@ -159,6 +162,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 +188,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 +240,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(
@@ -301,9 +330,10 @@ class MessageActionsModal extends StatelessWidget {
? editMessageInputBuilder(context, message)
: MessageInput(
editMessage: message,
onMessageSent: (_) {
preMessageSending: (m) {
FocusScope.of(context).unfocus();
Navigator.pop(context);
return m;
},
),
),
-4
View File
@@ -1976,10 +1976,6 @@ class MessageInputState extends State<MessageInput> {
return sendingFuture.then((resp) {
if (widget.onMessageSent != null) {
widget.onMessageSent(resp.message);
} else {
if (widget.editMessage != null) {
Navigator.pop(context);
}
}
});
}
+6 -4
View File
@@ -110,14 +110,16 @@ class MessageReactionsModal extends StatelessWidget {
translateUserAvatar: false,
showSendingIndicator: DisplayWidget.gone,
shape: messageShape,
showReactionPickerIndicator: true,
showInChannelIndicator: false,
showReactionPickerIndicator: showReactions &&
(message.status ==
MessageSendingStatus.SENT ||
message.status == null),
),
),
);
}),
SizedBox(
height: 16,
),
SizedBox(height: 8),
if (message.latestReactions?.isNotEmpty == true)
_buildReactionCard(context),
],
+89 -120
View File
@@ -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<Read> 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<String, AttachmentBuilder> customAttachmentBuilders,
this.showResendMessage = true,
this.readList,
this.padding,
this.textPadding = const EdgeInsets.symmetric(
@@ -224,11 +228,27 @@ 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 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
@@ -253,6 +273,7 @@ class _MessageWidgetState extends State<MessageWidget> {
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Stack(
clipBehavior: Clip.none,
alignment: AlignmentDirectional.bottomStart,
children: [
Column(
@@ -298,9 +319,7 @@ class _MessageWidgetState extends State<MessageWidget> {
)
: EdgeInsets.zero,
child: (widget.message.isDeleted &&
widget.message.status !=
MessageSendingStatus
.FAILED_DELETE)
!isFailedState)
? Transform(
alignment: Alignment.center,
transform: Matrix4.rotationY(
@@ -383,18 +402,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),
),
),
],
),
],
@@ -461,14 +485,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,
@@ -619,14 +643,19 @@ class _MessageWidgetState extends State<MessageWidget> {
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 +758,6 @@ class _MessageWidgetState extends State<MessageWidget> {
final attachmentShape =
widget.attachmentShape ?? widget.shape ?? _getDefaultShape(context);
return GestureDetector(
onTap: () => retryMessage(context),
onLongPress: () => onLongPress(context),
child: Material(
color: _getBackgroundColor(),
@@ -744,17 +772,7 @@ class _MessageWidgetState extends State<MessageWidget> {
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,
],
),
child: attachmentWidget,
),
),
),
@@ -777,14 +795,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).accentColor,
),
),
SizedBox(width: 2),
child,
],
);
}
return child;
}
Widget _buildUserAvatar() => Transform(
@@ -809,55 +840,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) {
Widget child = Transform(
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
@@ -867,26 +849,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) ==
@@ -896,7 +872,6 @@ class _MessageWidgetState extends State<MessageWidget> {
),
);
return GestureDetector(
onTap: () => retryMessage(context),
onLongPress: () => onLongPress(context),
child: child,
);
@@ -907,12 +882,6 @@ class _MessageWidgetState extends State<MessageWidget> {
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) {
+9 -16
View File
@@ -4,27 +4,29 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Used to show the sending status of the message
class SendingIndicator extends StatelessWidget {
final Message message;
final bool allRead;
final bool isMessageRead;
final double size;
const SendingIndicator({
Key key,
this.message,
this.allRead = false,
this.isMessageRead = false,
this.size = 12,
}) : super(key: key);
@override
Widget build(BuildContext context) {
if (allRead) {
if (isMessageRead) {
return Icon(
Icons.done_all,
size: 8,
Icons.done_all_rounded,
size: size,
color: StreamChatTheme.of(context).accentColor,
);
}
if (message.status == MessageSendingStatus.SENT || message.status == null) {
return Icon(
Icons.done,
size: 8,
size: size,
color: IconTheme.of(context).color.withOpacity(0.5),
);
}
@@ -32,18 +34,9 @@ class SendingIndicator extends StatelessWidget {
message.status == MessageSendingStatus.UPDATING) {
return Icon(
Icons.access_time,
size: 8,
size: size,
);
}
if (message.status == MessageSendingStatus.FAILED ||
message.status == MessageSendingStatus.FAILED_UPDATE ||
message.status == MessageSendingStatus.FAILED_DELETE) {
return Icon(
Icons.error_outline,
size: 8,
);
}
return SizedBox();
}
}
+24
View File
@@ -757,4 +757,28 @@ 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,
);
}
}