@@ -298,6 +298,43 @@ class _MessageListState extends State<MessageList> with PerAccountStoreAwareStat
298298 Widget _buildListView (BuildContext context) {
299299 final length = model! .items.length;
300300 const centerSliverKey = ValueKey ('center sliver' );
301+
302+ final sliver = SliverStickyHeaderList (
303+ headerPlacement: HeaderPlacement .scrollingStart,
304+ delegate: SliverChildBuilderDelegate (
305+ // To preserve state across rebuilds for individual [MessageItem]
306+ // widgets as the size of [MessageListView.items] changes we need
307+ // to match old widgets by their key to their new position in
308+ // the list.
309+ //
310+ // The keys are of type [ValueKey] with a value of [Message.id]
311+ // and here we use a O(log n) binary search method. This could
312+ // be improved but for now it only triggers for materialized
313+ // widgets. As a simple test, flinging through Combined feed in
314+ // CZO on a Pixel 5, this only runs about 10 times per rebuild
315+ // and the timing for each call is <100 microseconds.
316+ //
317+ // Non-message items (e.g., start and end markers) that do not
318+ // have state that needs to be preserved have not been given keys
319+ // and will not trigger this callback.
320+ findChildIndexCallback: (Key key) {
321+ final valueKey = key as ValueKey <int >;
322+ final index = model! .findItemWithMessageId (valueKey.value);
323+ if (index == - 1 ) return null ;
324+ return length - 1 - (index - 2 );
325+ },
326+ childCount: length + 2 ,
327+ (context, i) {
328+ // To reinforce that the end of the feed has been reached:
329+ // https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/flutter.3A.20Mark-as-read/near/1680603
330+ if (i == 0 ) return const SizedBox (height: 36 );
331+
332+ if (i == 1 ) return MarkAsReadWidget (narrow: widget.narrow);
333+
334+ final data = model! .items[length - 1 - (i - 2 )];
335+ return _buildItem (data, i);
336+ }));
337+
301338 return CustomScrollView (
302339 // TODO: Offer `ScrollViewKeyboardDismissBehavior.interactive` (or
303340 // similar) if that is ever offered:
@@ -316,41 +353,7 @@ class _MessageListState extends State<MessageList> with PerAccountStoreAwareStat
316353 center: centerSliverKey,
317354
318355 slivers: [
319- SliverStickyHeaderList (
320- headerPlacement: HeaderPlacement .scrollingStart,
321- delegate: SliverChildBuilderDelegate (
322- // To preserve state across rebuilds for individual [MessageItem]
323- // widgets as the size of [MessageListView.items] changes we need
324- // to match old widgets by their key to their new position in
325- // the list.
326- //
327- // The keys are of type [ValueKey] with a value of [Message.id]
328- // and here we use a O(log n) binary search method. This could
329- // be improved but for now it only triggers for materialized
330- // widgets. As a simple test, flinging through Combined feed in
331- // CZO on a Pixel 5, this only runs about 10 times per rebuild
332- // and the timing for each call is <100 microseconds.
333- //
334- // Non-message items (e.g., start and end markers) that do not
335- // have state that needs to be preserved have not been given keys
336- // and will not trigger this callback.
337- findChildIndexCallback: (Key key) {
338- final valueKey = key as ValueKey <int >;
339- final index = model! .findItemWithMessageId (valueKey.value);
340- if (index == - 1 ) return null ;
341- return length - 1 - (index - 2 );
342- },
343- childCount: length + 2 ,
344- (context, i) {
345- // To reinforce that the end of the feed has been reached:
346- // https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/flutter.3A.20Mark-as-read/near/1680603
347- if (i == 0 ) return const SizedBox (height: 36 );
348-
349- if (i == 1 ) return MarkAsReadWidget (narrow: widget.narrow);
350-
351- final data = model! .items[length - 1 - (i - 2 )];
352- return _buildItem (data, i);
353- })),
356+ sliver,
354357
355358 // This is a trivial placeholder that occupies no space. Its purpose is
356359 // to have the key that's passed to [ScrollView.center], and so to cause
0 commit comments