@@ -145,6 +145,26 @@ mixin _MessageSequence {
145145 /// The corresponding item index is [middleItem] .
146146 int middleMessage = 0 ;
147147
148+ /// The ID of the oldest known message so far in this narrow.
149+ ///
150+ /// This will be `null` if no messages of this narrow are fetched yet.
151+ /// Having a non-null value for this doesn't always mean [haveOldest] is `true` .
152+ ///
153+ /// The related message may not appear in [messages] because it
154+ /// is muted in one way or another.
155+ int ? get oldMessageId => _oldMessageId;
156+ int ? _oldMessageId;
157+
158+ /// The ID of the newest known message so far in this narrow.
159+ ///
160+ /// This will be `null` if no messages of this narrow are fetched yet.
161+ /// Having a non-null value for this doesn't always mean [haveNewest] is `true` .
162+ ///
163+ /// The related message may not appear in [messages] because it
164+ /// is muted in one way or another.
165+ int ? get newMessageId => _newMessageId;
166+ int ? _newMessageId;
167+
148168 /// Whether [messages] and [items] represent the results of a fetch.
149169 ///
150170 /// This allows the UI to distinguish "still working on fetching messages"
@@ -409,6 +429,8 @@ mixin _MessageSequence {
409429 generation += 1 ;
410430 messages.clear ();
411431 middleMessage = 0 ;
432+ _oldMessageId = null ;
433+ _newMessageId = null ;
412434 outboxMessages.clear ();
413435 _haveOldest = false ;
414436 _haveNewest = false ;
@@ -775,6 +797,7 @@ class MessageListView with ChangeNotifier, _MessageSequence {
775797 Future <void > fetchInitial () async {
776798 assert (! fetched && ! haveOldest && ! haveNewest && ! busyFetchingMore);
777799 assert (messages.isEmpty && contents.isEmpty);
800+ assert (oldMessageId == null && newMessageId == null );
778801
779802 if (narrow case KeywordSearchNarrow (keyword: '' )) {
780803 // The server would reject an empty keyword search; skip the request.
@@ -798,6 +821,9 @@ class MessageListView with ChangeNotifier, _MessageSequence {
798821 );
799822 if (this .generation > generation) return ;
800823
824+ _oldMessageId = result.messages.firstOrNull? .id;
825+ _newMessageId = result.messages.lastOrNull? .id;
826+
801827 _adjustNarrowForTopicPermalink (result.messages.firstOrNull);
802828
803829 store.reconcileMessages (result.messages);
@@ -869,25 +895,32 @@ class MessageListView with ChangeNotifier, _MessageSequence {
869895 /// That makes this method suitable to call frequently, e.g. every frame,
870896 /// whenever it looks likely to be useful to have more messages.
871897 Future <void > fetchOlder () async {
872- if (haveOldest) return ;
873- if (busyFetchingMore) return ;
874- assert (fetched);
875- assert (messages.isNotEmpty);
876- await _fetchMore (
877- anchor: NumericAnchor (messages[0 ].id),
878- numBefore: kMessageListFetchBatchSize,
879- numAfter: 0 ,
880- processResult: (result) {
881- store.reconcileMessages (result.messages);
882- store.recentSenders.handleMessages (result.messages); // TODO(#824)
883-
884- final fetchedMessages = _allMessagesVisible
885- ? result.messages // Avoid unnecessarily copying the list.
886- : result.messages.where (_messageVisible);
887-
888- _insertAllMessages (0 , fetchedMessages);
889- _haveOldest = result.foundOldest;
890- });
898+ final generation = this .generation;
899+ int visibleMessageCount = 0 ;
900+ do {
901+ if (haveOldest) return ;
902+ if (busyFetchingMore) return ;
903+ assert (fetched);
904+ assert (oldMessageId != null );
905+ await _fetchMore (
906+ anchor: NumericAnchor (oldMessageId! ),
907+ numBefore: kMessageListFetchBatchSize,
908+ numAfter: 0 ,
909+ processResult: (result) {
910+ _oldMessageId = result.messages.firstOrNull? .id ?? oldMessageId;
911+ store.reconcileMessages (result.messages);
912+ store.recentSenders.handleMessages (result.messages); // TODO(#824)
913+
914+ final fetchedMessages = _allMessagesVisible
915+ ? result.messages // Avoid unnecessarily copying the list.
916+ : result.messages.where (_messageVisible);
917+
918+ _insertAllMessages (0 , fetchedMessages);
919+ _haveOldest = result.foundOldest;
920+ visibleMessageCount += fetchedMessages.length;
921+ });
922+ } while (visibleMessageCount < kMessageListFetchBatchSize / 2
923+ && this .generation == generation);
891924 }
892925
893926 /// Fetch the next batch of newer messages, if applicable.
@@ -899,29 +932,36 @@ class MessageListView with ChangeNotifier, _MessageSequence {
899932 /// That makes this method suitable to call frequently, e.g. every frame,
900933 /// whenever it looks likely to be useful to have more messages.
901934 Future <void > fetchNewer () async {
902- if (haveNewest) return ;
903- if (busyFetchingMore) return ;
904- assert (fetched);
905- assert (messages.isNotEmpty);
906- await _fetchMore (
907- anchor: NumericAnchor (messages.last.id),
908- numBefore: 0 ,
909- numAfter: kMessageListFetchBatchSize,
910- processResult: (result) {
911- store.reconcileMessages (result.messages);
912- store.recentSenders.handleMessages (result.messages); // TODO(#824)
913-
914- for (final message in result.messages) {
915- if (_messageVisible (message)) {
916- _addMessage (message);
935+ final generation = this .generation;
936+ int visibleMessageCount = 0 ;
937+ do {
938+ if (haveNewest) return ;
939+ if (busyFetchingMore) return ;
940+ assert (fetched);
941+ assert (newMessageId != null );
942+ await _fetchMore (
943+ anchor: NumericAnchor (newMessageId! ),
944+ numBefore: 0 ,
945+ numAfter: kMessageListFetchBatchSize,
946+ processResult: (result) {
947+ _newMessageId = result.messages.lastOrNull? .id ?? newMessageId;
948+ store.reconcileMessages (result.messages);
949+ store.recentSenders.handleMessages (result.messages); // TODO(#824)
950+
951+ for (final message in result.messages) {
952+ if (_messageVisible (message)) {
953+ _addMessage (message);
954+ visibleMessageCount++ ;
955+ }
917956 }
918- }
919- _haveNewest = result.foundNewest;
957+ _haveNewest = result.foundNewest;
920958
921- if (haveNewest) {
922- _syncOutboxMessagesFromStore ();
923- }
924- });
959+ if (haveNewest) {
960+ _syncOutboxMessagesFromStore ();
961+ }
962+ });
963+ } while (visibleMessageCount < kMessageListFetchBatchSize / 2
964+ && this .generation == generation);
925965 }
926966
927967 Future <void > _fetchMore ({
0 commit comments