[MessageListView] : Use notification listener to implement pagination.

Signed-off-by: Sahil Kumar <[email protected]>
This commit is contained in:
Sahil Kumar
2020-12-03 17:28:38 +05:30
parent f367e6b7a8
commit 5cd9653c34
2 changed files with 114 additions and 118 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ class LazyLoadScrollView extends StatefulWidget {
final VoidCallback onEndOfPage; final VoidCallback onEndOfPage;
/// The offset to take into account when triggering [onEndOfPage] in pixels /// The offset to take into account when triggering [onEndOfPage] in pixels
final int scrollOffset; final double scrollOffset;
/// Used to determine if loading of new data has finished. You should use set this if you aren't using a FutureBuilder or StreamBuilder /// Used to determine if loading of new data has finished. You should use set this if you aren't using a FutureBuilder or StreamBuilder
final bool isLoading; final bool isLoading;
+113 -117
View File
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart'; import 'package:jiffy/jiffy.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart';
import 'package:stream_chat_flutter/src/message_widget.dart'; import 'package:stream_chat_flutter/src/message_widget.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; 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';
@@ -160,7 +161,6 @@ class MessageListView extends StatefulWidget {
class _MessageListViewState extends State<MessageListView> { class _MessageListViewState extends State<MessageListView> {
ItemScrollController _scrollController; ItemScrollController _scrollController;
bool _bottomWasVisible = false; bool _bottomWasVisible = false;
bool _topWasVisible = false;
Function _onThreadTap; Function _onThreadTap;
bool _showScrollToBottom = false; bool _showScrollToBottom = false;
ItemPositionsListener _itemPositionListener; ItemPositionsListener _itemPositionListener;
@@ -223,110 +223,121 @@ class _MessageListViewState extends State<MessageListView> {
return Stack( return Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
ScrollablePositionedList.builder( LazyLoadScrollView(
itemPositionsListener: _itemPositionListener, onStartOfPage: () => _paginateData(
addAutomaticKeepAlives: true, streamChannel,
key: Key('messageListView'), QueryDirection.bottom,
initialScrollIndex: widget.initialScrollIndex, ),
initialAlignment: widget.initialAlignment, onEndOfPage: () => _paginateData(
physics: widget.scrollPhysics, streamChannel,
itemScrollController: _scrollController, QueryDirection.top,
reverse: true, ),
itemCount: messages.length + child: ScrollablePositionedList.builder(
1 + itemPositionsListener: _itemPositionListener,
(widget.parentMessage != null ? 1 : 0), addAutomaticKeepAlives: true,
itemBuilder: (context, i) { key: Key('messageListView'),
if (i == messages.length + 1) { initialScrollIndex: widget.initialScrollIndex,
if (widget.parentMessageBuilder != null) { initialAlignment: widget.initialAlignment,
return widget.parentMessageBuilder( physics: widget.scrollPhysics,
itemScrollController: _scrollController,
reverse: true,
itemCount: messages.length +
1 +
(widget.parentMessage != null ? 1 : 0),
itemBuilder: (context, i) {
if (i == messages.length + 1) {
if (widget.parentMessageBuilder != null) {
return widget.parentMessageBuilder(
context,
widget.parentMessage,
);
} else {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
buildParentMessage(widget.parentMessage),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 32),
child: Container(
padding: const EdgeInsets.all(8),
child: Text(
'Start of thread',
textAlign: TextAlign.center,
),
color:
Theme.of(context).accentColor.withAlpha(50),
),
),
],
);
}
}
if (i == messages.length) {
return _buildLoadingIndicator(streamChannel);
}
final message = messages[i];
final nextMessage = i > 0 ? messages[i - 1] : null;
Widget messageWidget;
if (i == 0) {
messageWidget = _buildBottomMessage(
context, context,
widget.parentMessage, message,
messages,
streamChannel,
);
} else if (i == messages.length - 1) {
messageWidget = _buildTopMessage(
context,
message,
messages,
streamChannel,
); );
} else { } else {
if (widget.messageBuilder != null) {
messageWidget = Builder(
key: ValueKey<String>('MESSAGE-${message.id}'),
builder: (_) => widget.messageBuilder(
context,
MessageDetails(
context,
message,
messages,
i,
),
messages),
);
} else {
messageWidget = buildMessage(message, messages, i);
}
}
if (nextMessage != null &&
!Jiffy(message.createdAt.toLocal()).isSame(
nextMessage.createdAt.toLocal(), Units.DAY)) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[ children: <Widget>[
buildParentMessage(widget.parentMessage), messageWidget,
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 32), padding: const EdgeInsets.symmetric(vertical: 12.0),
child: Container( child: widget.dateDividerBuilder != null
padding: const EdgeInsets.all(8), ? widget.dateDividerBuilder(
child: Text( nextMessage.createdAt.toLocal())
'Start of thread', : DateDivider(
textAlign: TextAlign.center, dateTime: nextMessage.createdAt.toLocal(),
), ),
color:
Theme.of(context).accentColor.withAlpha(50),
),
), ),
], ],
); );
} }
}
if (i == messages.length) { return messageWidget;
return _buildLoadingIndicator(streamChannel); },
} ),
final message = messages[i];
final nextMessage = i > 0 ? messages[i - 1] : null;
Widget messageWidget;
if (i == 0) {
messageWidget = _buildBottomMessage(
context,
message,
messages,
streamChannel,
);
} else if (i == messages.length - 1) {
messageWidget = _buildTopMessage(
context,
message,
messages,
streamChannel,
);
} else {
if (widget.messageBuilder != null) {
messageWidget = Builder(
key: ValueKey<String>('MESSAGE-${message.id}'),
builder: (_) => widget.messageBuilder(
context,
MessageDetails(
context,
message,
messages,
i,
),
messages),
);
} else {
messageWidget = buildMessage(message, messages, i);
}
}
if (nextMessage != null &&
!Jiffy(message.createdAt.toLocal())
.isSame(nextMessage.createdAt.toLocal(), Units.DAY)) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
messageWidget,
Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0),
child: widget.dateDividerBuilder != null
? widget.dateDividerBuilder(
nextMessage.createdAt.toLocal())
: DateDivider(
dateTime: nextMessage.createdAt.toLocal(),
),
),
],
);
}
return messageWidget;
},
), ),
if (widget.showScrollToBottom && _showScrollToBottom) if (widget.showScrollToBottom && _showScrollToBottom)
_buildScrollToBottom(), _buildScrollToBottom(),
@@ -365,6 +376,14 @@ class _MessageListViewState extends State<MessageListView> {
}); });
} }
void _paginateData(StreamChannelState channel, QueryDirection direction) {
if (widget.parentMessage == null) {
channel.queryMessages(direction: direction);
} else {
channel.getReplies(widget.parentMessage.id, direction: direction);
}
}
ItemPosition _getTopElement(Iterable<ItemPosition> values) { ItemPosition _getTopElement(Iterable<ItemPosition> values) {
return values return values
.where((ItemPosition position) => position.itemLeadingEdge < 0.9) .where((ItemPosition position) => position.itemLeadingEdge < 0.9)
@@ -485,22 +504,7 @@ class _MessageListViewState extends State<MessageListView> {
} else { } else {
messageWidget = buildMessage(message, messages, messages.length - 1); messageWidget = buildMessage(message, messages, messages.length - 1);
} }
return messageWidget;
return VisibilityDetector(
key: ValueKey<String>('TOP-MESSAGE'),
child: messageWidget,
onVisibilityChanged: (visibility) {
final topIsVisible = visibility.visibleBounds != Rect.zero;
if (topIsVisible && !_topWasVisible) {
if (widget.parentMessage == null) {
streamChannel.queryMessages();
} else {
streamChannel.getReplies(widget.parentMessage.id);
}
_topWasVisible = !topIsVisible;
}
},
);
} }
Widget _buildBottomMessage( Widget _buildBottomMessage(
@@ -533,14 +537,6 @@ class _MessageListViewState extends State<MessageListView> {
onVisibilityChanged: (visibility) { onVisibilityChanged: (visibility) {
final isVisible = visibility.visibleBounds != Rect.zero; final isVisible = visibility.visibleBounds != Rect.zero;
if (isVisible && !_bottomWasVisible) { if (isVisible && !_bottomWasVisible) {
if (widget.parentMessage == null) {
streamChannel.queryMessages(direction: QueryDirection.bottom);
} else {
streamChannel.getReplies(
widget.parentMessage.id,
direction: QueryDirection.bottom,
);
}
if (streamChannel.channel.config?.readEvents == true && if (streamChannel.channel.config?.readEvents == true &&
streamChannel.channel.state.unreadCount > 0) { streamChannel.channel.state.unreadCount > 0) {
streamChannel.channel.markRead(); streamChannel.channel.markRead();