@@ -279,7 +279,7 @@ class _MessageListState extends State<MessageList> with PerAccountStoreAwareStat
279
279
280
280
Widget _buildListView (context) {
281
281
final length = model! .items.length;
282
- return StickyHeaderListView . builder (
282
+ return CustomScrollView (
283
283
// TODO: Offer `ScrollViewKeyboardDismissBehavior.interactive` (or
284
284
// similar) if that is ever offered:
285
285
// https://github.com/flutter/flutter/issues/57609#issuecomment-1355340849
@@ -291,46 +291,53 @@ class _MessageListState extends State<MessageList> with PerAccountStoreAwareStat
291
291
_ => ScrollViewKeyboardDismissBehavior .manual,
292
292
},
293
293
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
- },
315
294
controller: scrollController,
316
- itemCount : length + 2 ,
295
+
317
296
// 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.
320
299
// This works great when we want to start from the latest.
321
300
// TODO handle scroll starting at first unread, or link anchor
322
301
// TODO on new message when scrolled up, anchor scroll to what's in view
323
302
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 );
328
303
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
+ ]);
334
341
}
335
342
336
343
Widget _buildItem (MessageListItem data, int i) {
0 commit comments