add message failed handling

This commit is contained in:
Salvatore Giordano
2020-03-08 09:44:01 +01:00
parent 60c2ccfc24
commit 456e345558
10 changed files with 335 additions and 142 deletions
+12 -8
View File
@@ -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(),
), ),
), ),
), ),
+62 -6
View File
@@ -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) {
+24 -17
View File
@@ -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),
),
),
); );
} }
+8 -9
View File
@@ -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()),
),
);
}); });
} }
+8 -1
View File
@@ -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
View File
@@ -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,
); );
} }
+2 -1
View File
@@ -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,
+2 -1
View File
@@ -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);
} }
} }
+17 -6
View File
@@ -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
View File
@@ -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