diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index b5008054..3194827d 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -396,6 +396,12 @@ class _MessageListViewState extends State { _messageListLength = newMessagesListLength; + final itemCount = messages.length + // total messages + 2 + // top + bottom loading indicator + 2 + // header + footer + 1 // parent message + ; + return Stack( alignment: Alignment.center, children: [ @@ -416,12 +422,6 @@ class _MessageListViewState extends State { break; } - final itemCount = messages.length + // total messages - 2 + // top + bottom loading indicator - 2 + // header + footer - 1 // parent message - ; - return InfoTile( showMessage: widget.showConnectionStateTile && showStatus, tileAnchor: Alignment.topCenter, @@ -470,15 +470,15 @@ class _MessageListViewState extends State { // ParentMessage -> 7 (count-1) // Separator(ThreadSeparator) -> 6 (count-2) // Header -> 6 (count-2) - // Separator(Header -> 8??52) -> 5 (count-3) + // Separator(Header -> 8??T -> 0||52) -> 5 (count-3) // TopLoader -> 5 (count-3) - // Separator(2||8) -> 4 (count-4) + // Separator(0) -> 4 (count-4) // Message -> 4 (count-4) // Separator(2||8) -> 3 (count-5) // Message -> 3 (count-5) // Separator(2||8) -> 2 (count-6) // Message -> 2 (count-6) - // Separator(2||8) -> 1 (count-7) + // Separator(0) -> 1 (count-7) // BottomLoader -> 1 (count-7) // Separator(Footer -> 8??30) -> 0 (count-8) // Footer -> 0 (count-8) @@ -492,6 +492,7 @@ class _MessageListViewState extends State { } if (i == itemCount - 3) { if (widget.headerBuilder == null) { + if (_isThreadConversation) return const Offstage(); return const SizedBox(height: 52); } return const SizedBox(height: 8); @@ -505,8 +506,8 @@ class _MessageListViewState extends State { if (i == 1 || i == itemCount - 4) return const Offstage(); - final message = messages[i - 2]; - final nextMessage = messages[i - 1]; + final message = messages[i - 1]; + final nextMessage = messages[i - 2]; if (!Jiffy(message.createdAt.toLocal()).isSame( nextMessage.createdAt.toLocal(), Units.DAY, @@ -575,8 +576,9 @@ class _MessageListViewState extends State { const Offstage(); } - final topMessageIndex = itemCount - 4; - const bottomMessageIndex = 2; + final topMessageIndex = itemCount - + 4; // 7 -> parent // 6 -> header // 5 -> loader + const bottomMessageIndex = 2; // 1 -> loader // 0 -> footer final message = messages[i - 2]; Widget messageWidget; @@ -587,6 +589,7 @@ class _MessageListViewState extends State { message, messages, streamChannel, + i, ); } else if (i == bottomMessageIndex) { messageWidget = _buildBottomMessage( @@ -594,6 +597,7 @@ class _MessageListViewState extends State { message, messages, streamChannel!, + i, ); } else { if (widget.messageBuilder != null) { @@ -621,7 +625,8 @@ class _MessageListViewState extends State { }, ), if (widget.showScrollToBottom) _buildScrollToBottom(), - if (widget.showFloatingDateDivider) _buildFloatingDateDivider(), + if (widget.showFloatingDateDivider) + _buildFloatingDateDivider(itemCount), ], ); } @@ -647,42 +652,34 @@ class _MessageListViewState extends State { ); } - Positioned _buildFloatingDateDivider() => Positioned( + Positioned _buildFloatingDateDivider(int itemCount) => Positioned( top: 20, child: BetterStreamBuilder>( initialData: _itemPositionListener.itemPositions.value, stream: _itemPositionStream, comparator: (a, b) { - if (a == null) { + if (a == null || b == null) { return false; } - final aTop = _getTopElement(a)?.index; - final bTop = _getTopElement(b)?.index; + final aTop = _getTopElementIndex(a); + final bTop = _getTopElementIndex(b); return aTop == bTop; }, builder: (context, values) { - final items = _itemPositionListener.itemPositions.value; - if (items.isEmpty || messages.isEmpty) { - return const SizedBox(); + if (values.isEmpty || messages.isEmpty) { + return const Offstage(); } - var index = _getTopElement(values)?.index; + final index = _getTopElementIndex(values); - if (index == null || index > messages.length) { - return const SizedBox(); - } - - if (index == messages.length) { - index = max(index - 1, 0); + if (index == null || index <= 2 || index >= itemCount - 3) { + return const Offstage(); } + final message = messages[index - 2]; return widget.dateDividerBuilder != null - ? widget.dateDividerBuilder!( - messages[index].createdAt.toLocal(), - ) - : DateDivider( - dateTime: messages[index].createdAt.toLocal(), - ); + ? widget.dateDividerBuilder!(message.createdAt.toLocal()) + : DateDivider(dateTime: message.createdAt.toLocal()); }, ), ); @@ -691,16 +688,13 @@ class _MessageListViewState extends State { StreamChannelState? channel, QueryDirection direction) => _messageListController.paginateData!(direction: direction); - ItemPosition? _getTopElement(Iterable values) { - final inView = - values.where((ItemPosition position) => position.itemLeadingEdge < 0.9); - - if (inView.isEmpty) { - return null; - } - - return inView.reduce((ItemPosition max, ItemPosition position) => - position.itemLeadingEdge > max.itemLeadingEdge ? position : max); + int? _getTopElementIndex(Iterable values) { + final inView = values.where((position) => position.itemLeadingEdge < 1); + if (inView.isEmpty) return null; + return inView + .reduce((max, position) => + position.itemLeadingEdge > max.itemLeadingEdge ? position : max) + .index; } Widget _buildScrollToBottom() => StreamBuilder>( @@ -796,6 +790,7 @@ class _MessageListViewState extends State { Message message, List messages, StreamChannelState? streamChannel, + int index, ) { Widget messageWidget; if (widget.messageBuilder != null) { @@ -813,7 +808,7 @@ class _MessageListViewState extends State { ), ); } else { - messageWidget = buildMessage(message, messages, messages.length - 1); + messageWidget = buildMessage(message, messages, index); } return messageWidget; } @@ -823,6 +818,7 @@ class _MessageListViewState extends State { Message message, List messages, StreamChannelState streamChannel, + int index, ) { Widget messageWidget; if (widget.messageBuilder != null) { @@ -840,7 +836,7 @@ class _MessageListViewState extends State { ), ); } else { - messageWidget = buildMessage(message, messages, 0); + messageWidget = buildMessage(message, messages, index); } return VisibilityDetector( @@ -952,7 +948,7 @@ class _MessageListViewState extends State { final userId = StreamChat.of(context).user!.id; final isMyMessage = message.user!.id == userId; - final nextMessage = index - 2 >= 0 ? messages[index - 2] : null; + final nextMessage = index - 3 >= 0 ? messages[index - 3] : null; final isNextUserSame = nextMessage != null && message.user!.id == nextMessage.user!.id; diff --git a/packages/stream_chat_flutter_core/lib/src/better_stream_builder.dart b/packages/stream_chat_flutter_core/lib/src/better_stream_builder.dart index ddf342dc..871a8a1d 100644 --- a/packages/stream_chat_flutter_core/lib/src/better_stream_builder.dart +++ b/packages/stream_chat_flutter_core/lib/src/better_stream_builder.dart @@ -25,7 +25,7 @@ class BetterStreamBuilder extends StatefulWidget { final T initialData; /// Comparator used to check if the new data is different than the last one - final bool Function(T?, T)? comparator; + final bool Function(T?, T?)? comparator; /// Builder that builds based on the new snapshot final Widget Function(BuildContext context, T data) builder; @@ -96,9 +96,8 @@ class _BetterStreamBuilderState extends State> { void _onEvent(T event) { _lastError = null; - final isEqual = widget.comparator != null - ? widget.comparator!(_lastEvent, event) - : event == _lastEvent; + final isEqual = + widget.comparator?.call(_lastEvent, event) ?? event == _lastEvent; if (!isEqual) { if (mounted) { setState(() {});