lint changes mlv

This commit is contained in:
Deven Joshi
2021-05-05 15:53:08 +05:30
parent bda64e8764
commit 160baa5d95
@@ -12,34 +12,60 @@ import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/src/system_message.dart'; import 'package:stream_chat_flutter/src/system_message.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:visibility_detector/visibility_detector.dart'; import 'package:visibility_detector/visibility_detector.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/swipeable.dart';
import '../stream_chat_flutter.dart'; /// Widget builder for message
import 'connection_status_builder.dart';
import 'date_divider.dart';
import 'extension.dart';
import 'swipeable.dart';
typedef MessageBuilder = Widget Function( typedef MessageBuilder = Widget Function(
BuildContext, BuildContext,
MessageDetails, MessageDetails,
List<Message>, List<Message>,
); );
/// Widget builder for parent message
typedef ParentMessageBuilder = Widget Function( typedef ParentMessageBuilder = Widget Function(
BuildContext, BuildContext,
Message?, Message?,
); );
/// Widget builder for system message
typedef SystemMessageBuilder = Widget Function( typedef SystemMessageBuilder = Widget Function(
BuildContext, BuildContext,
Message, Message,
); );
/// Widget builder for thread
typedef ThreadBuilder = Widget Function(BuildContext context, Message? parent); typedef ThreadBuilder = Widget Function(BuildContext context, Message? parent);
/// Callback for thread taps
typedef ThreadTapCallback = void Function(Message, Widget?); typedef ThreadTapCallback = void Function(Message, Widget?);
/// Callback on message swiped
typedef OnMessageSwiped = void Function(Message); typedef OnMessageSwiped = void Function(Message);
/// Callback on message tapped
typedef OnMessageTap = void Function(Message); typedef OnMessageTap = void Function(Message);
/// Callback on reply tapped
typedef ReplyTapCallback = void Function(Message); typedef ReplyTapCallback = void Function(Message);
/// Class for message details
class MessageDetails { class MessageDetails {
/// Constructor for creating [MessageDetails]
MessageDetails(
BuildContext context,
this.message,
List<Message> messages,
this.index,
) {
isMyMessage = message.user?.id == StreamChat.of(context).user?.id;
isLastUser = index + 1 < messages.length &&
message.user?.id == messages[index + 1].user?.id;
isNextUser =
index - 1 >= 0 && message.user!.id == messages[index - 1].user?.id;
}
/// True if the message belongs to the current user /// True if the message belongs to the current user
bool? isMyMessage; bool? isMyMessage;
@@ -54,19 +80,6 @@ class MessageDetails {
/// The index of the message /// The index of the message
int index; int index;
MessageDetails(
BuildContext context,
this.message,
List<Message> messages,
this.index,
) {
isMyMessage = message.user?.id == StreamChat.of(context).user?.id;
isLastUser = index + 1 < messages.length &&
message.user?.id == messages[index + 1].user?.id;
isNextUser =
index - 1 >= 0 && message.user!.id == messages[index - 1].user?.id;
}
} }
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_listview.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_listview.png)
@@ -104,10 +117,12 @@ class MessageDetails {
/// ``` /// ```
/// ///
/// ///
/// Make sure to have a [StreamChannel] ancestor in order to provide the information about the channels. /// Make sure to have a [StreamChannel] ancestor in order to
/// provide the information about the channels.
/// The widget uses a [ListView.custom] to render the list of channels. /// The widget uses a [ListView.custom] to render the list of channels.
/// ///
/// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. /// The widget components render the ui based on the first
/// ancestor of type [StreamChatTheme].
/// Modify it to change the widget appearance. /// Modify it to change the widget appearance.
class MessageListView extends StatefulWidget { class MessageListView extends StatefulWidget {
/// Instantiate a new MessageListView /// Instantiate a new MessageListView
@@ -158,10 +173,12 @@ class MessageListView extends StatefulWidget {
final ThreadBuilder? threadBuilder; final ThreadBuilder? threadBuilder;
/// Function called when tapping on a thread /// Function called when tapping on a thread
/// By default it calls [Navigator.push] using the widget built using [threadBuilder] /// By default it calls [Navigator.push] using the widget
/// built using [threadBuilder]
final ThreadTapCallback? onThreadTap; final ThreadTapCallback? onThreadTap;
/// If true will show a scroll to bottom message when there are new messages and the scroll offset is not zero /// If true will show a scroll to bottom message when there are new
/// messages and the scroll offset is not zero
final bool showScrollToBottom; final bool showScrollToBottom;
/// Parent message in case of a thread /// Parent message in case of a thread
@@ -201,8 +218,10 @@ class MessageListView extends StatefulWidget {
/// Color used while highlighting initial message /// Color used while highlighting initial message
final Color? messageHighlightColor; final Color? messageHighlightColor;
/// Callback when show message is tapped
final ShowMessageCallback? onShowMessage; final ShowMessageCallback? onShowMessage;
/// Flag for showing tile on header
final bool showConnectionStateTile; final bool showConnectionStateTile;
/// Function called when messages are fetched /// Function called when messages are fetched
@@ -214,8 +233,10 @@ class MessageListView extends StatefulWidget {
/// Function used to build an empty widget /// Function used to build an empty widget
final WidgetBuilder? emptyBuilder; final WidgetBuilder? emptyBuilder;
/// Callback triggered when an error occurs while performing the given request. /// Callback triggered when an error occurs while performing the
/// This parameter can be used to display an error message to users in the event /// given request.
/// This parameter can be used to display an error message to
/// users in the event
/// of a connection failure. /// of a connection failure.
final ErrorBuilder? errorWidgetBuilder; final ErrorBuilder? errorWidgetBuilder;
@@ -223,10 +244,12 @@ class MessageListView extends StatefulWidget {
final bool Function(Message)? messageFilter; final bool Function(Message)? messageFilter;
/// Attachment builders for the default message widget /// Attachment builders for the default message widget
/// Please change this in the [MessageWidget] if you are using a custom implementation /// Please change this in the [MessageWidget] if you are using a
/// custom implementation
final Map<String, AttachmentBuilder>? customAttachmentBuilders; final Map<String, AttachmentBuilder>? customAttachmentBuilders;
/// Called when any message is tapped except a system message (use [onSystemMessageTap] instead) /// Called when any message is tapped except a system message
/// (use [onSystemMessageTap] instead)
final OnMessageTap? onMessageTap; final OnMessageTap? onMessageTap;
/// Called when system message is tapped /// Called when system message is tapped
@@ -238,6 +261,7 @@ class MessageListView extends StatefulWidget {
/// Customize the MessageWidget textBuilder /// Customize the MessageWidget textBuilder
final void Function(BuildContext context, Message message)? textBuilder; final void Function(BuildContext context, Message message)? textBuilder;
/// Callback for when link is tapped
final void Function(String link)? onLinkTap; final void Function(String link)? onLinkTap;
@override @override
@@ -257,9 +281,8 @@ class _MessageListViewState extends State<MessageListView> {
if (streamChannel!.initialMessageId != null) { if (streamChannel!.initialMessageId != null) {
final messages = streamChannel!.channel.state!.messages; final messages = streamChannel!.channel.state!.messages;
final totalMessages = messages.length; final totalMessages = messages.length;
final messageIndex = messages.indexWhere((e) { final messageIndex =
return e.id == streamChannel!.initialMessageId; messages.indexWhere((e) => e.id == streamChannel!.initialMessageId);
});
final index = totalMessages - messageIndex; final index = totalMessages - messageIndex;
if (index != 0) return index - 1; if (index != 0) return index - 1;
return index; return index;
@@ -272,9 +295,7 @@ class _MessageListViewState extends State<MessageListView> {
return 0; return 0;
} }
bool _isInitialMessage(String id) { bool _isInitialMessage(String id) => streamChannel!.initialMessageId == id;
return streamChannel!.initialMessageId == id;
}
bool get _upToDate => streamChannel!.channel.state!.isUpToDate; bool get _upToDate => streamChannel!.channel.state!.isUpToDate;
@@ -295,50 +316,46 @@ class _MessageListViewState extends State<MessageListView> {
final MessageListController _messageListController = MessageListController(); final MessageListController _messageListController = MessageListController();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => MessageListCore(
return MessageListCore( messageFilter: widget.messageFilter,
messageFilter: widget.messageFilter, loadingBuilder: widget.loadingBuilder ??
loadingBuilder: widget.loadingBuilder ?? (context) => const Center(
(context) { child: CircularProgressIndicator(),
return Center( ),
child: const CircularProgressIndicator(), emptyBuilder: widget.emptyBuilder ??
); (context) => Center(
}, child: Text(
emptyBuilder: widget.emptyBuilder ?? 'No chats here yet...',
(context) { style: StreamChatTheme.of(context)
return Center( .textTheme
child: Text( .footnote
'No chats here yet...', .copyWith(
style: StreamChatTheme.of(context).textTheme.footnote.copyWith( color: StreamChatTheme.of(context)
color: StreamChatTheme.of(context) .colorTheme
.colorTheme .black
.black .withOpacity(.5)),
.withOpacity(.5)), ),
), ),
); messageListBuilder: widget.messageListBuilder ??
}, (context, list) => _buildListView(list),
messageListBuilder: widget.messageListBuilder ?? messageListController: _messageListController,
(context, list) { parentMessage: widget.parentMessage,
return _buildListView(list); showScrollToBottom: widget.showScrollToBottom,
}, errorWidgetBuilder: widget.errorWidgetBuilder ??
messageListController: _messageListController, (BuildContext context, Object error) => Center(
parentMessage: widget.parentMessage, child: Text(
showScrollToBottom: widget.showScrollToBottom, 'Something went wrong',
errorWidgetBuilder: widget.errorWidgetBuilder ?? style: StreamChatTheme.of(context)
(BuildContext context, Object error) { .textTheme
return Center( .footnote
child: Text( .copyWith(
'Something went wrong', color: StreamChatTheme.of(context)
style: StreamChatTheme.of(context).textTheme.footnote.copyWith( .colorTheme
color: StreamChatTheme.of(context) .black
.colorTheme .withOpacity(.5)),
.black ),
.withOpacity(.5)), ),
), );
);
},
);
}
Widget _buildListView(List<Message> data) { Widget _buildListView(List<Message> data) {
messages = data; messages = data;
@@ -384,6 +401,7 @@ class _MessageListViewState extends State<MessageListView> {
} }
return InfoTile( return InfoTile(
// ignore: avoid_bool_literals_in_conditional_expressions
showMessage: widget.showConnectionStateTile ? showStatus : false, showMessage: widget.showConnectionStateTile ? showStatus : false,
tileAnchor: Alignment.topCenter, tileAnchor: Alignment.topCenter,
childAnchor: Alignment.topCenter, childAnchor: Alignment.topCenter,
@@ -426,8 +444,8 @@ class _MessageListViewState extends State<MessageListView> {
itemCount: itemCount:
messages.length + 2 + (_isThreadConversation ? 1 : 0), messages.length + 2 + (_isThreadConversation ? 1 : 0),
separatorBuilder: (context, i) { separatorBuilder: (context, i) {
if (i == messages.length) return Offstage(); if (i == messages.length) return const Offstage();
if (i == 0) return SizedBox(height: 30); if (i == 0) return const SizedBox(height: 30);
if (i == messages.length + 1) { if (i == messages.length + 1) {
final replyCount = widget.parentMessage!.replyCount; final replyCount = widget.parentMessage!.replyCount;
return Container( return Container(
@@ -438,6 +456,7 @@ class _MessageListViewState extends State<MessageListView> {
child: Padding( child: Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Text( child: Text(
// ignore: lines_longer_than_80_chars
'$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}', '$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: StreamChatTheme.of(context) style: StreamChatTheme.of(context)
@@ -481,9 +500,9 @@ class _MessageListViewState extends State<MessageListView> {
!isNextUserSame || !isNextUserSame ||
isThread || isThread ||
isDeleted) { isDeleted) {
return SizedBox(height: 8); return const SizedBox(height: 8);
} }
return SizedBox(height: 2); return const SizedBox(height: 2);
}, },
itemBuilder: (context, i) { itemBuilder: (context, i) {
if (i == messages.length + 2) { if (i == messages.length + 2) {
@@ -559,13 +578,13 @@ class _MessageListViewState extends State<MessageListView> {
builder: (context, values, _) { builder: (context, values, _) {
final items = _itemPositionListener.itemPositions.value; final items = _itemPositionListener.itemPositions.value;
if (items.isEmpty || messages.isEmpty) { if (items.isEmpty || messages.isEmpty) {
return SizedBox(); return const SizedBox();
} }
var index = _getTopElement(values).index; var index = _getTopElement(values).index;
if (index > messages.length) { if (index > messages.length) {
return SizedBox(); return const SizedBox();
} }
if (index == messages.length) { if (index == messages.length) {
@@ -587,95 +606,90 @@ class _MessageListViewState extends State<MessageListView> {
} }
Future<void> _paginateData( Future<void> _paginateData(
StreamChannelState? channel, QueryDirection direction) { StreamChannelState? channel, QueryDirection direction) =>
return _messageListController.paginateData!(direction: direction); _messageListController.paginateData!(direction: direction);
}
ItemPosition _getTopElement(Iterable<ItemPosition> values) { ItemPosition _getTopElement(Iterable<ItemPosition> values) => values
return values .where((ItemPosition position) => position.itemLeadingEdge < 0.9)
.where((ItemPosition position) => position.itemLeadingEdge < 0.9) .reduce((ItemPosition max, ItemPosition position) =>
.reduce((ItemPosition max, ItemPosition position) => position.itemLeadingEdge > max.itemLeadingEdge ? position : max);
position.itemLeadingEdge > max.itemLeadingEdge ? position : max);
}
Widget _buildScrollToBottom() { Widget _buildScrollToBottom() => StreamBuilder<Tuple2<bool, int>>(
return StreamBuilder<Tuple2<bool, int>>( stream: Rx.combineLatest2(
stream: Rx.combineLatest2( streamChannel!.channel.state!.isUpToDateStream,
streamChannel!.channel.state!.isUpToDateStream, streamChannel!.channel.state!.unreadCountStream,
streamChannel!.channel.state!.unreadCountStream, (bool isUpToDate, int unreadCount) => Tuple2(isUpToDate, unreadCount),
(bool isUpToDate, int unreadCount) => Tuple2(isUpToDate, unreadCount), ),
), builder: (_, snapshot) {
builder: (_, snapshot) { if (snapshot.hasError) {
if (snapshot.hasError) { return const Offstage();
return Offstage(); } else if (!snapshot.hasData) {
} else if (!snapshot.hasData) { return const Offstage();
return Offstage(); }
} final isUpToDate = snapshot.data!.item1;
final isUpToDate = snapshot.data!.item1; final showScrollToBottom = !isUpToDate || _showScrollToBottom;
final showScrollToBottom = !isUpToDate || _showScrollToBottom; if (!showScrollToBottom) {
if (!showScrollToBottom) { return const Offstage();
return Offstage(); }
} final unreadCount = snapshot.data!.item2;
final unreadCount = snapshot.data!.item2; final showUnreadCount = unreadCount > 0 &&
final showUnreadCount = unreadCount > 0 && streamChannel!.channel.state!.members.any((e) =>
streamChannel!.channel.state!.members.any((e) => e.userId == streamChannel!.channel.client.state.user!.id);
e.userId == streamChannel!.channel.client.state.user!.id); return Positioned(
return Positioned( bottom: 8,
bottom: 8, right: 8,
right: 8, width: 40,
width: 40, height: 40,
height: 40, child: Stack(
child: Stack( clipBehavior: Clip.none,
clipBehavior: Clip.none, children: [
children: [ FloatingActionButton(
FloatingActionButton( backgroundColor: StreamChatTheme.of(context).colorTheme.white,
backgroundColor: StreamChatTheme.of(context).colorTheme.white, onPressed: () {
onPressed: () { if (unreadCount > 0) {
if (unreadCount > 0) { streamChannel!.channel.markRead();
streamChannel!.channel.markRead(); }
} if (!_upToDate) {
if (!_upToDate) { _bottomPaginationActive = false;
_bottomPaginationActive = false; _topPaginationActive = false;
_topPaginationActive = false; streamChannel!.reloadChannel();
streamChannel!.reloadChannel(); } else {
} else { setState(() => _showScrollToBottom = false);
setState(() => _showScrollToBottom = false); _scrollController!.scrollTo(
_scrollController!.scrollTo( index: 0,
index: 0, duration: const Duration(seconds: 1),
duration: Duration(seconds: 1), curve: Curves.easeInOut,
curve: Curves.easeInOut, );
); }
} },
}, child: StreamSvgIcon.down(
child: StreamSvgIcon.down( color: StreamChatTheme.of(context).colorTheme.black,
color: StreamChatTheme.of(context).colorTheme.black, ),
), ),
), if (showUnreadCount)
if (showUnreadCount) Positioned(
Positioned( width: 20,
width: 20, height: 20,
height: 20, left: 10,
left: 10, top: -10,
top: -10, child: CircleAvatar(
child: CircleAvatar( child: Padding(
child: Padding( padding: const EdgeInsets.all(3),
padding: const EdgeInsets.all(3), child: Text(
child: Text( '$unreadCount',
'$unreadCount', style: const TextStyle(
style: TextStyle( fontSize: 11,
fontSize: 11, fontWeight: FontWeight.bold,
fontWeight: FontWeight.bold, ),
), ),
), ),
), ),
), ),
), ],
], ),
), );
); },
}, );
);
}
Widget _buildLoadingIndicator( Widget _buildLoadingIndicator(
StreamChannelState? streamChannel, StreamChannelState? streamChannel,
@@ -685,7 +699,7 @@ class _MessageListViewState extends State<MessageListView> {
? streamChannel!.queryTopMessages ? streamChannel!.queryTopMessages
: streamChannel!.queryBottomMessages; : streamChannel!.queryBottomMessages;
return StreamBuilder<bool>( return StreamBuilder<bool>(
key: Key('LOADING-INDICATOR'), key: const Key('LOADING-INDICATOR'),
stream: stream, stream: stream,
initialData: false, initialData: false,
builder: (context, snapshot) { builder: (context, snapshot) {
@@ -695,24 +709,24 @@ class _MessageListViewState extends State<MessageListView> {
.colorTheme .colorTheme
.accentRed .accentRed
.withOpacity(.2), .withOpacity(.2),
child: Center( child: const Center(
child: Text('Error loading messages'), child: Text('Error loading messages'),
), ),
); );
} }
if (!snapshot.data!) { if (!snapshot.data!) {
if (!_isThreadConversation && direction == QueryDirection.top) { if (!_isThreadConversation && direction == QueryDirection.top) {
return Container( return const SizedBox(
height: 52, height: 52,
width: double.infinity, width: double.infinity,
); );
} }
return Offstage(); return const Offstage();
} }
return Center( return const Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(8), padding: EdgeInsets.all(8),
child: const CircularProgressIndicator(), child: CircularProgressIndicator(),
), ),
); );
}, },
@@ -728,7 +742,7 @@ class _MessageListViewState extends State<MessageListView> {
Widget messageWidget; Widget messageWidget;
if (widget.messageBuilder != null) { if (widget.messageBuilder != null) {
messageWidget = Builder( messageWidget = Builder(
key: ValueKey<String>('TOP-MESSAGE'), key: const ValueKey<String>('TOP-MESSAGE'),
builder: (_) => widget.messageBuilder!( builder: (_) => widget.messageBuilder!(
context, context,
MessageDetails( MessageDetails(
@@ -810,7 +824,7 @@ class _MessageListViewState extends State<MessageListView> {
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
showSendingIndicator: false, showSendingIndicator: false,
onThreadTap: _onThreadTap as void Function(Message)?, onThreadTap: _onThreadTap as void Function(Message)?,
borderRadiusGeometry: BorderRadius.only( borderRadiusGeometry: const BorderRadius.only(
topLeft: Radius.circular(16), topLeft: Radius.circular(16),
bottomLeft: Radius.circular(2), bottomLeft: Radius.circular(2),
topRight: Radius.circular(16), topRight: Radius.circular(16),
@@ -886,8 +900,8 @@ class _MessageListViewState extends State<MessageListView> {
final channel = streamChannel!.channel; final channel = streamChannel!.channel;
final readList = channel.state?.read?.where((read) { final readList = channel.state?.read?.where((read) {
if (read.user.id == userId) return false; if (read.user.id == userId) return false;
return (read.lastRead.isAfter(message.createdAt) || return read.lastRead.isAfter(message.createdAt) ||
read.lastRead.isAtSameMomentAs(message.createdAt)); read.lastRead.isAtSameMomentAs(message.createdAt);
}).toList() ?? }).toList() ??
[]; [];
@@ -945,6 +959,7 @@ class _MessageListViewState extends State<MessageListView> {
showSendingIndicator: showSendingIndicator, showSendingIndicator: showSendingIndicator,
showUserAvatar: showUserAvatar, showUserAvatar: showUserAvatar,
onQuotedMessageTap: (quotedMessageId) async { onQuotedMessageTap: (quotedMessageId) async {
// ignore: prefer_function_declarations_over_variables
final scrollToIndex = () { final scrollToIndex = () {
final index = messages.indexWhere((m) => m.id == quotedMessageId); final index = messages.indexWhere((m) => m.id == quotedMessageId);
_scrollController?.scrollTo( _scrollController?.scrollTo(
@@ -984,14 +999,14 @@ class _MessageListViewState extends State<MessageListView> {
), ),
attachmentPadding: EdgeInsets.all(hasFileAttachment ? 4 : 2), attachmentPadding: EdgeInsets.all(hasFileAttachment ? 4 : 2),
borderRadiusGeometry: BorderRadius.only( borderRadiusGeometry: BorderRadius.only(
topLeft: Radius.circular(16), topLeft: const Radius.circular(16),
bottomLeft: Radius.circular( bottomLeft: Radius.circular(
(timeDiff >= 1 || !isNextUserSame) && !(hasReplies || isThreadMessage) (timeDiff >= 1 || !isNextUserSame) && !(hasReplies || isThreadMessage)
? 0 ? 0
: 16, : 16,
), ),
topRight: Radius.circular(16), topRight: const Radius.circular(16),
bottomRight: Radius.circular(16), bottomRight: const Radius.circular(16),
), ),
textPadding: EdgeInsets.symmetric( textPadding: EdgeInsets.symmetric(
vertical: 8, vertical: 8,
@@ -1031,7 +1046,7 @@ class _MessageListViewState extends State<MessageListView> {
!message.isEphemeral && !message.isEphemeral &&
widget.onMessageSwiped != null) { widget.onMessageSwiped != null) {
child = Container( child = Container(
decoration: BoxDecoration(), decoration: const BoxDecoration(),
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
child: Swipeable( child: Swipeable(
onSwipeEnd: () { onSwipeEnd: () {
@@ -1059,12 +1074,10 @@ class _MessageListViewState extends State<MessageListView> {
), ),
duration: const Duration(seconds: 3), duration: const Duration(seconds: 3),
onEnd: () => initialMessageHighlightComplete = true, onEnd: () => initialMessageHighlightComplete = true,
builder: (_, color, child) { builder: (_, color, child) => Container(
return Container( color: color,
color: color, child: child,
child: child, ),
);
},
child: Padding( child: Padding(
padding: const EdgeInsets.only(top: 4), padding: const EdgeInsets.only(top: 4),
child: child, child: child,
@@ -1124,19 +1137,18 @@ class _MessageListViewState extends State<MessageListView> {
_onThreadTap = (Message message) { _onThreadTap = (Message message) {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute(builder: (_) { MaterialPageRoute(
return StreamBuilder<Message>( builder: (_) => StreamBuilder<Message>(
stream: streamChannel!.channel.state!.messagesStream.map( stream: streamChannel!.channel.state!.messagesStream.map(
(messages) => (messages) =>
messages!.firstWhere((m) => m.id == message.id)), messages!.firstWhere((m) => m.id == message.id)),
initialData: message, initialData: message,
builder: (_, snapshot) { builder: (_, snapshot) => StreamChannel(
return StreamChannel( channel: streamChannel!.channel,
channel: streamChannel!.channel, child: widget.threadBuilder!(context, snapshot.data),
child: widget.threadBuilder!(context, snapshot.data), ),
); ),
}); ),
}),
); );
}; };
} }