Merge pull request #189 from GetStream/messg-error-state
Message error state
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user