implemented backward pagination for chat history with on-scroll prefetching
This commit is contained in:
@@ -42,9 +42,15 @@ class ChatView extends StatefulWidget {
|
||||
|
||||
class _ChatViewState extends State<ChatView> with RouteAware {
|
||||
final ItemScrollController _itemScrollController = ItemScrollController();
|
||||
final ItemPositionsListener _positionsListener =
|
||||
ItemPositionsListener.create();
|
||||
final TextEditingController _searchTextController = TextEditingController();
|
||||
final Map<int, int> _matchIndices = {};
|
||||
|
||||
// Number of rows currently rendered; kept in sync in build so the scroll
|
||||
// listener can tell when the oldest row (highest index, reverse list) nears.
|
||||
int _itemCount = 0;
|
||||
|
||||
bool _searchActive = false;
|
||||
String _searchQuery = '';
|
||||
List<ChatSearchMatch> _matches = const [];
|
||||
@@ -63,9 +69,24 @@ class _ChatViewState extends State<ChatView> with RouteAware {
|
||||
super.initState();
|
||||
_chatBlocRef = context.read<ChatBloc>();
|
||||
_chatListBlocRef = context.read<ChatListBloc>();
|
||||
_positionsListener.itemPositions.addListener(_onScrollPositions);
|
||||
NotificationTasks.clearNotificationsForChat(widget.room.token);
|
||||
}
|
||||
|
||||
/// Loads the next older block once the top of the list comes into view.
|
||||
void _onScrollPositions() {
|
||||
final positions = _positionsListener.itemPositions.value;
|
||||
if (positions.isEmpty) return;
|
||||
// reverse:true → the highest index is the oldest message (top of screen).
|
||||
// Prefetch ~a screenful ahead so the next block is usually already merged
|
||||
// before the user reaches the top — the load stays invisible.
|
||||
final maxIndex = positions.map((p) => p.index).reduce(math.max);
|
||||
if (maxIndex < _itemCount - _kLoadOlderPrefetchRows) return;
|
||||
final data = _chatBlocRef?.state.data;
|
||||
if (data == null || !data.hasMoreOld || data.isLoadingOlder) return;
|
||||
_chatBlocRef?.loadOlder();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
@@ -93,6 +114,7 @@ class _ChatViewState extends State<ChatView> with RouteAware {
|
||||
if (_subscribedRoute != null) {
|
||||
AppRoutes.chatRouteObserver.unsubscribe(this);
|
||||
}
|
||||
_positionsListener.itemPositions.removeListener(_onScrollPositions);
|
||||
_markAsReadFinal();
|
||||
_chatBlocRef?.leaveChat(widget.room.token);
|
||||
_searchTextController.dispose();
|
||||
@@ -281,22 +303,6 @@ class _ChatViewState extends State<ChatView> with RouteAware {
|
||||
);
|
||||
}
|
||||
|
||||
if (response.data.length >= 200) {
|
||||
messages.insert(
|
||||
0,
|
||||
ChatBubble(
|
||||
isSender: false,
|
||||
bubbleData: GetChatResponseObject.getTextDummy(
|
||||
'Zurzeit können in dieser App nur die letzten 200 vergangenen Nachrichten angezeigt werden. '
|
||||
'Um ältere Nachrichten abzurufen verwende die Webversion unter https://cloud.marianum-fulda.de',
|
||||
),
|
||||
chatData: widget.room,
|
||||
refetch: ({bool renew = false}) => _refresh(),
|
||||
),
|
||||
);
|
||||
chronologicalMatchIndex.updateAll((_, v) => v + 1);
|
||||
}
|
||||
|
||||
final total = messages.length;
|
||||
_matchIndices
|
||||
..clear()
|
||||
@@ -373,9 +379,27 @@ class _ChatViewState extends State<ChatView> with RouteAware {
|
||||
final items = _buildMessages(
|
||||
state.chatResponse!,
|
||||
).reversed.toList();
|
||||
// reverse:true renders index 0 at the bottom, so the top
|
||||
// marker (spinner / start-of-chat) goes at the end.
|
||||
if (state.isLoadingOlder) {
|
||||
items.add(const _LoadingOlderIndicator());
|
||||
} else if (!state.hasMoreOld) {
|
||||
items.add(
|
||||
ChatBubble(
|
||||
isSender: false,
|
||||
bubbleData: GetChatResponseObject.getTextDummy(
|
||||
'Anfang des Chats',
|
||||
),
|
||||
chatData: widget.room,
|
||||
refetch: ({bool renew = false}) => _refresh(),
|
||||
),
|
||||
);
|
||||
}
|
||||
_itemCount = items.length;
|
||||
return ScrollablePositionedList.builder(
|
||||
reverse: true,
|
||||
itemScrollController: _itemScrollController,
|
||||
itemPositionsListener: _positionsListener,
|
||||
itemCount: items.length,
|
||||
itemBuilder: (ctx, idx) => items[idx],
|
||||
);
|
||||
@@ -405,3 +429,22 @@ class _ChatViewState extends State<ChatView> with RouteAware {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// How many rows before the oldest one the older-history prefetch kicks in.
|
||||
const _kLoadOlderPrefetchRows = 15;
|
||||
|
||||
class _LoadingOlderIndicator extends StatelessWidget {
|
||||
const _LoadingOlderIndicator();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 12),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 22,
|
||||
height: 22,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user