add message failed handling
This commit is contained in:
+12
-8
@@ -23,6 +23,11 @@ class MyApp extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
|
theme: ThemeData(
|
||||||
|
appBarTheme: AppBarTheme(
|
||||||
|
brightness: Brightness.light,
|
||||||
|
),
|
||||||
|
),
|
||||||
home: Container(
|
home: Container(
|
||||||
child: StreamChat(
|
child: StreamChat(
|
||||||
client: client,
|
client: client,
|
||||||
@@ -75,14 +80,13 @@ class ChannelPage extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: Align(
|
child: Padding(
|
||||||
alignment: Alignment.bottomRight,
|
padding: const EdgeInsets.symmetric(
|
||||||
child: Padding(
|
horizontal: 8.0,
|
||||||
padding: const EdgeInsets.symmetric(
|
vertical: 4,
|
||||||
horizontal: 8.0,
|
),
|
||||||
vertical: 4,
|
child: TypingIndicator(
|
||||||
),
|
alignment: Alignment.bottomRight,
|
||||||
child: TypingIndicator(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -55,8 +55,11 @@ class ChannelListView extends StatefulWidget {
|
|||||||
this.onChannelTap,
|
this.onChannelTap,
|
||||||
this.channelWidget,
|
this.channelWidget,
|
||||||
this.channelPreviewBuilder,
|
this.channelPreviewBuilder,
|
||||||
|
this.errorBuilder,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final Widget Function(Error error) errorBuilder;
|
||||||
|
|
||||||
/// The query filters to use.
|
/// The query filters to use.
|
||||||
/// You can query on any of the custom fields you've defined on the [Channel].
|
/// You can query on any of the custom fields you've defined on the [Channel].
|
||||||
/// You can also filter other built-in channel fields.
|
/// You can also filter other built-in channel fields.
|
||||||
@@ -114,16 +117,69 @@ class _ChannelListViewState extends State<ChannelListView> {
|
|||||||
child: StreamBuilder<List<Channel>>(
|
child: StreamBuilder<List<Channel>>(
|
||||||
stream: streamChat.channelsStream,
|
stream: streamChat.channelsStream,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData) {
|
if (snapshot.hasError) {
|
||||||
|
if (snapshot.error is Error) {
|
||||||
|
print((snapshot.error as Error).stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.errorBuilder != null) {
|
||||||
|
return widget.errorBuilder(snapshot.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
String message = snapshot.error.toString();
|
||||||
|
if (snapshot.error is DioError) {
|
||||||
|
final dioError = snapshot.error as DioError;
|
||||||
|
if (dioError.type == DioErrorType.RESPONSE) {
|
||||||
|
message = dioError.message;
|
||||||
|
} else {
|
||||||
|
message = 'Check your connection and retry';
|
||||||
|
}
|
||||||
|
}
|
||||||
return Center(
|
return Center(
|
||||||
child: CircularProgressIndicator(),
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
WidgetSpan(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
right: 2.0,
|
||||||
|
),
|
||||||
|
child: Icon(Icons.error_outline),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(text: 'Error loading channels'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
style: Theme.of(context).textTheme.title,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: 16.0,
|
||||||
|
),
|
||||||
|
child: Text(message),
|
||||||
|
),
|
||||||
|
FlatButton(
|
||||||
|
onPressed: () {
|
||||||
|
streamChat.queryChannels(
|
||||||
|
filter: widget.filter,
|
||||||
|
sortOptions: widget.sort,
|
||||||
|
paginationParams: widget.pagination,
|
||||||
|
options: widget.options,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Text('Retry'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snapshot.hasError) {
|
if (!snapshot.hasData) {
|
||||||
print((snapshot.error as Error).stackTrace);
|
|
||||||
return Center(
|
return Center(
|
||||||
child: Text(snapshot.error.toString()),
|
child: CircularProgressIndicator(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +215,7 @@ class _ChannelListViewState extends State<ChannelListView> {
|
|||||||
if (i < channels.length) {
|
if (i < channels.length) {
|
||||||
final channel = channels[i];
|
final channel = channels[i];
|
||||||
|
|
||||||
final channelClient = streamChat.client.channels[channel.id];
|
final channelClient = streamChat.client.channels[channel.cid];
|
||||||
|
|
||||||
ChannelTapCallback onTap;
|
ChannelTapCallback onTap;
|
||||||
if (widget.onChannelTap != null) {
|
if (widget.onChannelTap != null) {
|
||||||
|
|||||||
@@ -42,11 +42,22 @@ class ChannelPreview extends StatelessWidget {
|
|||||||
textStyle: StreamChatTheme.of(context).channelPreviewTheme.title,
|
textStyle: StreamChatTheme.of(context).channelPreviewTheme.title,
|
||||||
),
|
),
|
||||||
subtitle: _buildSubtitle(context),
|
subtitle: _buildSubtitle(context),
|
||||||
trailing: Column(
|
trailing: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
_buildDate(context),
|
_buildDate(context),
|
||||||
|
if (channel.state.unreadCount > 0)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
|
child: CircleAvatar(
|
||||||
|
backgroundColor: Color(0xffd0021B),
|
||||||
|
radius: 6,
|
||||||
|
child: Text(
|
||||||
|
'${channel.state.unreadCount}',
|
||||||
|
style: TextStyle(fontSize: 8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -80,20 +91,16 @@ class ChannelPreview extends StatelessWidget {
|
|||||||
|
|
||||||
Widget _buildSubtitle(BuildContext context) {
|
Widget _buildSubtitle(BuildContext context) {
|
||||||
final opacity = channel.state.unreadCount > 0 ? 1.0 : 0.5;
|
final opacity = channel.state.unreadCount > 0 ? 1.0 : 0.5;
|
||||||
return Align(
|
return TypingIndicator(
|
||||||
alignment: Alignment.centerLeft,
|
channel: channel,
|
||||||
child: TypingIndicator(
|
alternativeWidget: _buildLastMessage(context, opacity),
|
||||||
channel: channel,
|
style: StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
|
||||||
alternativeWidget: _buildLastMessage(context, opacity),
|
color: StreamChatTheme.of(context)
|
||||||
style:
|
.channelPreviewTheme
|
||||||
StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
|
.subtitle
|
||||||
color: StreamChatTheme.of(context)
|
.color
|
||||||
.channelPreviewTheme
|
.withOpacity(opacity),
|
||||||
.subtitle
|
),
|
||||||
.color
|
|
||||||
.withOpacity(opacity),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -644,7 +644,7 @@ class _MessageInputState extends State<MessageInput> {
|
|||||||
_mentionsOverlay?.remove();
|
_mentionsOverlay?.remove();
|
||||||
_mentionsOverlay = null;
|
_mentionsOverlay = null;
|
||||||
|
|
||||||
FocusScope.of(context).unfocus();
|
final channel = StreamChannel.of(context).channel;
|
||||||
|
|
||||||
Future sendingFuture;
|
Future sendingFuture;
|
||||||
Message message;
|
Message message;
|
||||||
@@ -659,6 +659,11 @@ class _MessageInputState extends State<MessageInput> {
|
|||||||
mentionedUsers:
|
mentionedUsers:
|
||||||
_mentionedUsers.where((u) => text.contains('@${u.name}')).toList(),
|
_mentionedUsers.where((u) => text.contains('@${u.name}')).toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (widget.editMessage.status == MessageSendingStatus.FAILED) {
|
||||||
|
sendingFuture = channel.sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
sendingFuture = StreamChat.of(context).client.updateMessage(message);
|
sendingFuture = StreamChat.of(context).client.updateMessage(message);
|
||||||
} else {
|
} else {
|
||||||
message = Message(
|
message = Message(
|
||||||
@@ -668,19 +673,13 @@ class _MessageInputState extends State<MessageInput> {
|
|||||||
mentionedUsers:
|
mentionedUsers:
|
||||||
_mentionedUsers.where((u) => text.contains('@${u.name}')).toList(),
|
_mentionedUsers.where((u) => text.contains('@${u.name}')).toList(),
|
||||||
);
|
);
|
||||||
sendingFuture = StreamChannel.of(context).channel.sendMessage(message);
|
sendingFuture = channel.sendMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendingFuture.then((_) {
|
sendingFuture.whenComplete(() {
|
||||||
if (widget.onMessageSent != null) {
|
if (widget.onMessageSent != null) {
|
||||||
widget.onMessageSent(message);
|
widget.onMessageSent(message);
|
||||||
}
|
}
|
||||||
}).catchError((error) {
|
|
||||||
Scaffold.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(error.toString()),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -318,7 +318,11 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
Stream<List<Message>> stream;
|
Stream<List<Message>> stream;
|
||||||
|
|
||||||
if (widget.parentMessage == null) {
|
if (widget.parentMessage == null) {
|
||||||
stream = streamChannel.channel.state.messagesStream;
|
stream = streamChannel.channel.state.messagesStream.map((messages) =>
|
||||||
|
messages
|
||||||
|
.where((m) => !(m.status == MessageSendingStatus.FAILED &&
|
||||||
|
m.type == 'deleted'))
|
||||||
|
.toList());
|
||||||
} else {
|
} else {
|
||||||
streamChannel.getReplies(widget.parentMessage.id);
|
streamChannel.getReplies(widget.parentMessage.id);
|
||||||
stream = streamChannel.channel.state.threadsStream
|
stream = streamChannel.channel.state.threadsStream
|
||||||
@@ -333,6 +337,9 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
newMessages.first.id != _messages.first.id) {
|
newMessages.first.id != _messages.first.id) {
|
||||||
if (!_scrollController.hasClients ||
|
if (!_scrollController.hasClients ||
|
||||||
_scrollController.offset < _newMessageLoadingOffset) {
|
_scrollController.offset < _newMessageLoadingOffset) {
|
||||||
|
if (streamChannel.channel.state.unreadCount > 0) {
|
||||||
|
streamChannel.channel.markRead();
|
||||||
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_messages = newMessages;
|
_messages = newMessages;
|
||||||
});
|
});
|
||||||
|
|||||||
+198
-92
@@ -122,7 +122,58 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
left: isMyMessage ? 8.0 : 0,
|
left: isMyMessage ? 8.0 : 0,
|
||||||
right: isMyMessage ? 0 : 8.0,
|
right: isMyMessage ? 0 : 8.0,
|
||||||
),
|
),
|
||||||
child: UserAvatar(user: widget.message.user),
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
UserAvatar(user: widget.message.user),
|
||||||
|
if (isMyMessage &&
|
||||||
|
widget.nextMessage == null &&
|
||||||
|
widget.message.status == MessageSendingStatus.SENT)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 1.0,
|
||||||
|
),
|
||||||
|
child: CircleAvatar(
|
||||||
|
radius: 4,
|
||||||
|
child: Icon(
|
||||||
|
Icons.done,
|
||||||
|
size: 4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (isMyMessage &&
|
||||||
|
widget.message.status == MessageSendingStatus.SENDING)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 1.0,
|
||||||
|
),
|
||||||
|
child: CircleAvatar(
|
||||||
|
radius: 4,
|
||||||
|
backgroundColor: Colors.grey,
|
||||||
|
child: Icon(
|
||||||
|
Icons.access_time,
|
||||||
|
size: 4,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (isMyMessage &&
|
||||||
|
widget.message.status == MessageSendingStatus.FAILED)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 1.0,
|
||||||
|
),
|
||||||
|
child: CircleAvatar(
|
||||||
|
radius: 4,
|
||||||
|
backgroundColor: Color(0xffd0021B).withAlpha(125),
|
||||||
|
child: Icon(
|
||||||
|
Icons.error_outline,
|
||||||
|
size: 4,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -131,7 +182,8 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
}
|
}
|
||||||
|
|
||||||
child = Container(
|
child = Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: (isMyMessage && widget.nextMessage == null) ? 0.0 : 10),
|
||||||
margin: EdgeInsets.only(
|
margin: EdgeInsets.only(
|
||||||
top: isLastUser ? 5 : 24,
|
top: isLastUser ? 5 : 24,
|
||||||
bottom: widget.nextMessage == null ? 30 : 0,
|
bottom: widget.nextMessage == null ? 30 : 0,
|
||||||
@@ -250,41 +302,52 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
attachmentWidget,
|
attachmentWidget,
|
||||||
attachment.title != null
|
attachment.title != null
|
||||||
? Container(
|
? GestureDetector(
|
||||||
constraints:
|
onTap: () {
|
||||||
BoxConstraints.loose(Size(300, 500)),
|
if (attachment.titleLink != null) {
|
||||||
child: Padding(
|
_launchURL(attachment.titleLink);
|
||||||
padding: const EdgeInsets.all(8.0),
|
}
|
||||||
child: Column(
|
},
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: Container(
|
||||||
crossAxisAlignment:
|
constraints:
|
||||||
CrossAxisAlignment.start,
|
BoxConstraints.loose(Size(300, 500)),
|
||||||
children: <Widget>[
|
child: Padding(
|
||||||
Text(
|
padding: const EdgeInsets.all(8.0),
|
||||||
attachment.title,
|
child: Column(
|
||||||
overflow: TextOverflow.ellipsis,
|
mainAxisSize: MainAxisSize.min,
|
||||||
style: messageTheme.messageText
|
crossAxisAlignment:
|
||||||
.copyWith(
|
CrossAxisAlignment.start,
|
||||||
color: Colors.blue,
|
children: <Widget>[
|
||||||
fontWeight: FontWeight.bold,
|
Text(
|
||||||
|
attachment.title,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: messageTheme.messageText
|
||||||
|
.copyWith(
|
||||||
|
color: Colors.blue,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
if (attachment.titleLink != null ||
|
||||||
Text(
|
attachment.ogScrapeUrl != null)
|
||||||
Uri.parse(attachment.thumbUrl)
|
Text(
|
||||||
.authority
|
Uri.parse(attachment
|
||||||
.split('.')
|
.titleLink ??
|
||||||
.reversed
|
attachment.ogScrapeUrl)
|
||||||
.take(2)
|
.authority
|
||||||
.toList()
|
.split('.')
|
||||||
.reversed
|
.reversed
|
||||||
.join('.'),
|
.take(2)
|
||||||
overflow: TextOverflow.ellipsis,
|
.toList()
|
||||||
style: messageTheme.createdAt,
|
.reversed
|
||||||
),
|
.join('.'),
|
||||||
],
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: messageTheme.createdAt,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
color: Color(0xffebebeb),
|
||||||
),
|
),
|
||||||
color: Color(0xffebebeb),
|
|
||||||
)
|
)
|
||||||
: SizedBox(),
|
: SizedBox(),
|
||||||
],
|
],
|
||||||
@@ -399,42 +462,58 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
isLastUser || nOfAttachmentWidgets > 0),
|
isLastUser || nOfAttachmentWidgets > 0),
|
||||||
padding: EdgeInsets.all(10),
|
padding: EdgeInsets.all(10),
|
||||||
constraints: BoxConstraints.loose(Size.fromWidth(300)),
|
constraints: BoxConstraints.loose(Size.fromWidth(300)),
|
||||||
child: MarkdownBody(
|
child: Column(
|
||||||
data: text,
|
mainAxisSize: MainAxisSize.min,
|
||||||
onTapLink: (link) {
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
if (link.startsWith('@')) {
|
children: <Widget>[
|
||||||
final mentionedUser =
|
if (widget.message.status ==
|
||||||
widget.message.mentionedUsers.firstWhere(
|
MessageSendingStatus.FAILED)
|
||||||
(u) => '@${u.name.replaceAll(' ', '')}' == link,
|
Text(
|
||||||
orElse: () => null,
|
'MESSAGE FAILED · CLICK TO TRY AGAIN',
|
||||||
);
|
style: TextStyle(
|
||||||
|
color: Colors.black.withOpacity(.5),
|
||||||
|
fontSize: 11,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MarkdownBody(
|
||||||
|
data: text,
|
||||||
|
onTapLink: (link) {
|
||||||
|
if (link.startsWith('@')) {
|
||||||
|
final mentionedUser =
|
||||||
|
widget.message.mentionedUsers.firstWhere(
|
||||||
|
(u) =>
|
||||||
|
'@${u.name.replaceAll(' ', '')}' == link,
|
||||||
|
orElse: () => null,
|
||||||
|
);
|
||||||
|
|
||||||
if (widget.onMentionTap != null) {
|
if (widget.onMentionTap != null) {
|
||||||
widget.onMentionTap(mentionedUser);
|
widget.onMentionTap(mentionedUser);
|
||||||
} else {
|
} else {
|
||||||
print('tap on ${mentionedUser.name}');
|
print('tap on ${mentionedUser.name}');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_launchURL(link);
|
_launchURL(link);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
styleSheet: MarkdownStyleSheet.fromTheme(
|
styleSheet: MarkdownStyleSheet.fromTheme(
|
||||||
Theme.of(context).copyWith(
|
Theme.of(context).copyWith(
|
||||||
textTheme: Theme.of(context).textTheme.apply(
|
textTheme: Theme.of(context).textTheme.apply(
|
||||||
bodyColor: messageTheme.messageText.color,
|
bodyColor: messageTheme.messageText.color,
|
||||||
decoration:
|
decoration:
|
||||||
messageTheme.messageText.decoration,
|
messageTheme.messageText.decoration,
|
||||||
decorationColor:
|
decorationColor: messageTheme
|
||||||
messageTheme.messageText.decorationColor,
|
.messageText.decorationColor,
|
||||||
decorationStyle:
|
decorationStyle: messageTheme
|
||||||
messageTheme.messageText.decorationStyle,
|
.messageText.decorationStyle,
|
||||||
fontFamily:
|
fontFamily:
|
||||||
messageTheme.messageText.fontFamily,
|
messageTheme.messageText.fontFamily,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
).copyWith(
|
||||||
|
p: messageTheme.messageText,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
).copyWith(
|
],
|
||||||
p: messageTheme.messageText,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -474,8 +553,15 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
child: IntrinsicWidth(child: column),
|
child: IntrinsicWidth(child: column),
|
||||||
|
onTap: () {
|
||||||
|
if (widget.message.status == MessageSendingStatus.FAILED) {
|
||||||
|
StreamChannel.of(context).channel.sendMessage(widget.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
if (widget.message.type == 'ephemeral') {
|
if (widget.message.type == 'ephemeral' ||
|
||||||
|
widget.message.status == MessageSendingStatus.SENDING) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,6 +592,8 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
@@ -523,7 +611,8 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(
|
Container(
|
||||||
color: Colors.black87,
|
color: Colors.black87,
|
||||||
child: streamChannel.channel.config.reactions
|
child: (streamChannel.channel.config.reactions &&
|
||||||
|
widget.message.status != MessageSendingStatus.FAILED)
|
||||||
? ReactionPicker(
|
? ReactionPicker(
|
||||||
channel: StreamChannel.of(context).channel,
|
channel: StreamChannel.of(context).channel,
|
||||||
reactionToEmoji: reactionToEmoji,
|
reactionToEmoji: reactionToEmoji,
|
||||||
@@ -537,17 +626,16 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
padding: const EdgeInsets.all(28.0),
|
padding: const EdgeInsets.all(28.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Delete message',
|
'Delete message',
|
||||||
style: Theme.of(context)
|
style: theme.textTheme.headline
|
||||||
.textTheme
|
|
||||||
.headline
|
|
||||||
.copyWith(color: Colors.red),
|
.copyWith(color: Colors.red),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
StreamChat.of(context)
|
|
||||||
.client
|
|
||||||
.deleteMessage(widget.message.id);
|
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
StreamChat.of(context).client.deleteMessage(
|
||||||
|
widget.message,
|
||||||
|
streamChannel.channel.cid,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: SizedBox(),
|
: SizedBox(),
|
||||||
@@ -557,7 +645,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
padding: const EdgeInsets.all(28.0),
|
padding: const EdgeInsets.all(28.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Edit message',
|
'Edit message',
|
||||||
style: Theme.of(context).textTheme.headline,
|
style: theme.textTheme.headline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
@@ -568,6 +656,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
)
|
)
|
||||||
: SizedBox(),
|
: SizedBox(),
|
||||||
(streamChannel.channel.config.replies &&
|
(streamChannel.channel.config.replies &&
|
||||||
|
widget.message.status != MessageSendingStatus.FAILED &&
|
||||||
widget.message.parentId == null &&
|
widget.message.parentId == null &&
|
||||||
!widget.isParent)
|
!widget.isParent)
|
||||||
? FlatButton(
|
? FlatButton(
|
||||||
@@ -575,7 +664,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
padding: const EdgeInsets.all(28.0),
|
padding: const EdgeInsets.all(28.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Start a thread',
|
'Start a thread',
|
||||||
style: Theme.of(context).textTheme.headline,
|
style: theme.textTheme.headline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@@ -667,6 +756,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
message.id == widget.message.parentId)
|
message.id == widget.message.parentId)
|
||||||
: null,
|
: null,
|
||||||
onMessageSent: (_) {
|
onMessageSent: (_) {
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -698,15 +788,14 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
borderRadius: BorderRadius.all(Radius.circular(14))),
|
borderRadius: BorderRadius.all(Radius.circular(14))),
|
||||||
child: (widget.message.reactionCounts != null &&
|
child: AnimatedSwitcher(
|
||||||
widget.message.reactionCounts.isNotEmpty)
|
duration: Duration(milliseconds: 300),
|
||||||
? AnimatedSize(
|
reverseDuration: Duration(milliseconds: 0),
|
||||||
duration: Duration(milliseconds: 300),
|
child: (widget.message.reactionCounts != null &&
|
||||||
vsync: this,
|
widget.message.reactionCounts.isNotEmpty)
|
||||||
curve: Curves.easeInQuad,
|
? _buildReactionRow()
|
||||||
child: _buildReactionRow(),
|
: SizedBox(),
|
||||||
)
|
),
|
||||||
: SizedBox(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -751,6 +840,19 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
return CachedNetworkImage(
|
return CachedNetworkImage(
|
||||||
imageUrl:
|
imageUrl:
|
||||||
attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl,
|
attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl,
|
||||||
|
errorWidget: (context, url, error) {
|
||||||
|
return Container(
|
||||||
|
width: 200,
|
||||||
|
height: 140,
|
||||||
|
color: Color(0xffd0021B).withAlpha(26),
|
||||||
|
child: Center(
|
||||||
|
child: Icon(
|
||||||
|
Icons.error_outline,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -829,10 +931,12 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
Widget _buildTimestamp(Alignment alignment) {
|
Widget _buildTimestamp(Alignment alignment) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(top: 5.0),
|
padding: const EdgeInsets.only(top: 5.0),
|
||||||
child: Text(
|
child: widget.message.createdAt != null
|
||||||
Jiffy(widget.message.createdAt.toLocal()).format('HH:mm'),
|
? Text(
|
||||||
style: messageTheme.createdAt,
|
Jiffy(widget.message.createdAt.toLocal()).format('HH:mm'),
|
||||||
),
|
style: messageTheme.createdAt,
|
||||||
|
)
|
||||||
|
: SizedBox(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -845,7 +949,9 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
topRight: Radius.circular((isMyMessage && isLastUser) ? 2 : 16),
|
topRight: Radius.circular((isMyMessage && isLastUser) ? 2 : 16),
|
||||||
bottomRight: Radius.circular(isMyMessage ? 2 : 16),
|
bottomRight: Radius.circular(isMyMessage ? 2 : 16),
|
||||||
),
|
),
|
||||||
color: messageTheme.messageBackgroundColor,
|
color: widget.message.status == MessageSendingStatus.FAILED
|
||||||
|
? Color(0xffd0021B).withAlpha(26)
|
||||||
|
: messageTheme.messageBackgroundColor,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ class ReactionPicker extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: reactionToEmoji.keys.map((reactionType) {
|
children: reactionToEmoji.keys.map((reactionType) {
|
||||||
final ownReactionIndex = message.ownReactions
|
final ownReactionIndex = message.ownReactions
|
||||||
.indexWhere((reaction) => reaction.type == reactionType);
|
?.indexWhere((reaction) => reaction.type == reactionType) ??
|
||||||
|
-1;
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
|||||||
@@ -226,9 +226,10 @@ class StreamChatState extends State<StreamChat> {
|
|||||||
);
|
);
|
||||||
channels.addAll(res);
|
channels.addAll(res);
|
||||||
_channelsController.sink.add(channels);
|
_channelsController.sink.add(channels);
|
||||||
_queryChannelsLoadingController.sink.add(false);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_channelsController.sink.addError(e);
|
_channelsController.sink.addError(e);
|
||||||
|
} finally {
|
||||||
|
_queryChannelsLoadingController.sink.add(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class TypingIndicator extends StatelessWidget {
|
|||||||
this.channel,
|
this.channel,
|
||||||
this.alternativeWidget = const SizedBox(),
|
this.alternativeWidget = const SizedBox(),
|
||||||
this.style,
|
this.style,
|
||||||
|
this.alignment = Alignment.centerLeft,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
/// Style of the text widget
|
/// Style of the text widget
|
||||||
@@ -20,6 +21,8 @@ class TypingIndicator extends StatelessWidget {
|
|||||||
/// Widget built when no typings is happening
|
/// Widget built when no typings is happening
|
||||||
final Widget alternativeWidget;
|
final Widget alternativeWidget;
|
||||||
|
|
||||||
|
final Alignment alignment;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final channelState =
|
final channelState =
|
||||||
@@ -31,13 +34,21 @@ class TypingIndicator extends StatelessWidget {
|
|||||||
return AnimatedSwitcher(
|
return AnimatedSwitcher(
|
||||||
duration: Duration(milliseconds: 300),
|
duration: Duration(milliseconds: 300),
|
||||||
child: snapshot.data.isNotEmpty
|
child: snapshot.data.isNotEmpty
|
||||||
? Text(
|
? Align(
|
||||||
'${snapshot.data.map((u) => u.name).join(',')} ${snapshot.data.length == 1 ? 'is' : 'are'} typing...',
|
key: Key('typings'),
|
||||||
maxLines: 1,
|
alignment: alignment,
|
||||||
style: style,
|
child: Text(
|
||||||
|
'${snapshot.data.map((u) => u.name).join(',')} ${snapshot.data.length == 1 ? 'is' : 'are'} typing...',
|
||||||
|
maxLines: 1,
|
||||||
|
style: style,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
: Container(
|
: Align(
|
||||||
child: alternativeWidget,
|
key: Key('alternative'),
|
||||||
|
alignment: alignment,
|
||||||
|
child: Container(
|
||||||
|
child: alternativeWidget,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
+2
-1
@@ -20,7 +20,8 @@ dependencies:
|
|||||||
file_picker: ^1.4.3+2
|
file_picker: ^1.4.3+2
|
||||||
image_picker: ^0.6.3+4
|
image_picker: ^0.6.3+4
|
||||||
keyboard_visibility: ^0.5.6
|
keyboard_visibility: ^0.5.6
|
||||||
stream_chat: ^0.1.17
|
stream_chat:
|
||||||
|
path: ../stream_chat_dart
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
pedantic: ^1.9.0
|
pedantic: ^1.9.0
|
||||||
|
|||||||
Reference in New Issue
Block a user