Skip to content

Commit 373f7fe

Browse files
committed
msglist [nfc]: Unpack to a CustomScrollView of a single sliver
This is effectively what StickyHeaderListView.builder was abbreviating. This expanded form will then give us flexibility to introduce a second sliver.
1 parent 8b7c576 commit 373f7fe

File tree

1 file changed

+41
-34
lines changed

1 file changed

+41
-34
lines changed

lib/widgets/message_list.dart

+41-34
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ class _MessageListState extends State<MessageList> with PerAccountStoreAwareStat
279279

280280
Widget _buildListView(context) {
281281
final length = model!.items.length;
282-
return StickyHeaderListView.builder(
282+
return CustomScrollView(
283283
// TODO: Offer `ScrollViewKeyboardDismissBehavior.interactive` (or
284284
// similar) if that is ever offered:
285285
// https://github.com/flutter/flutter/issues/57609#issuecomment-1355340849
@@ -291,46 +291,53 @@ class _MessageListState extends State<MessageList> with PerAccountStoreAwareStat
291291
_ => ScrollViewKeyboardDismissBehavior.manual,
292292
},
293293

294-
// To preserve state across rebuilds for individual [MessageItem]
295-
// widgets as the size of [MessageListView.items] changes we need
296-
// to match old widgets by their key to their new position in
297-
// the list.
298-
//
299-
// The keys are of type [ValueKey] with a value of [Message.id]
300-
// and here we use a O(log n) binary search method. This could
301-
// be improved but for now it only triggers for materialized
302-
// widgets. As a simple test, flinging through All Messages in
303-
// CZO on a Pixel 5, this only runs about 10 times per rebuild
304-
// and the timing for each call is <100 microseconds.
305-
//
306-
// Non-message items (e.g., start and end markers) that do not
307-
// have state that needs to be preserved have not been given keys
308-
// and will not trigger this callback.
309-
findChildIndexCallback: (Key key) {
310-
final valueKey = key as ValueKey;
311-
final index = model!.findItemWithMessageId(valueKey.value);
312-
if (index == -1) return null;
313-
return length - 1 - (index - 2);
314-
},
315294
controller: scrollController,
316-
itemCount: length + 2,
295+
317296
// Setting reverse: true means the scroll starts at the bottom.
318-
// Flipping the indexes (in itemBuilder) means the start/bottom
319-
// has the latest messages.
297+
// Flipping the indexes (in the SliverChildBuilderDelegate callback)
298+
// means the start/bottom has the latest messages.
320299
// This works great when we want to start from the latest.
321300
// TODO handle scroll starting at first unread, or link anchor
322301
// TODO on new message when scrolled up, anchor scroll to what's in view
323302
reverse: true,
324-
itemBuilder: (context, i) {
325-
// To reinforce that the end of the feed has been reached:
326-
// https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/flutter.3A.20Mark-as-read/near/1680603
327-
if (i == 0) return const SizedBox(height: 36);
328303

329-
if (i == 1) return MarkAsReadWidget(narrow: widget.narrow);
330-
331-
final data = model!.items[length - 1 - (i - 2)];
332-
return _buildItem(data, i);
333-
});
304+
slivers: [
305+
SliverStickyHeaderList(
306+
headerPlacement: HeaderPlacement.scrollingEnd,
307+
delegate: SliverChildBuilderDelegate(
308+
// To preserve state across rebuilds for individual [MessageItem]
309+
// widgets as the size of [MessageListView.items] changes we need
310+
// to match old widgets by their key to their new position in
311+
// the list.
312+
//
313+
// The keys are of type [ValueKey] with a value of [Message.id]
314+
// and here we use a O(log n) binary search method. This could
315+
// be improved but for now it only triggers for materialized
316+
// widgets. As a simple test, flinging through All Messages in
317+
// CZO on a Pixel 5, this only runs about 10 times per rebuild
318+
// and the timing for each call is <100 microseconds.
319+
//
320+
// Non-message items (e.g., start and end markers) that do not
321+
// have state that needs to be preserved have not been given keys
322+
// and will not trigger this callback.
323+
findChildIndexCallback: (Key key) {
324+
final valueKey = key as ValueKey;
325+
final index = model!.findItemWithMessageId(valueKey.value);
326+
if (index == -1) return null;
327+
return length - 1 - (index - 2);
328+
},
329+
childCount: length + 2,
330+
(context, i) {
331+
// To reinforce that the end of the feed has been reached:
332+
// https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/flutter.3A.20Mark-as-read/near/1680603
333+
if (i == 0) return const SizedBox(height: 36);
334+
335+
if (i == 1) return MarkAsReadWidget(narrow: widget.narrow);
336+
337+
final data = model!.items[length - 1 - (i - 2)];
338+
return _buildItem(data, i);
339+
})),
340+
]);
334341
}
335342

336343
Widget _buildItem(MessageListItem data, int i) {

0 commit comments

Comments
 (0)