@@ -145,6 +145,32 @@ 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 is used as the anchor for fetching the next batch of older messages
151+ /// and will be `null` if no messages of this narrow have been fetched yet.
152+ /// A non-null value for this doesn't always mean [haveOldest] is `true` .
153+ ///
154+ /// The related message may not appear in [messages] because it
155+ /// is muted in one way or another. Also, the related message may not stay
156+ /// in the narrow for reasons such as message moves or deletions,
157+ /// but that's fine for what this is used for.
158+ int ? get oldMessageId => _oldMessageId;
159+ int ? _oldMessageId;
160+
161+ /// The ID of the newest known message so far in this narrow.
162+ ///
163+ /// This is used as the anchor for fetching the next batch of newer messages
164+ /// and will be `null` if no messages of this narrow have been fetched yet.
165+ /// A non-null value for this doesn't always mean [haveNewest] is `true` .
166+ ///
167+ /// The related message may not appear in [messages] because it
168+ /// is muted in one way or another. Also, the related message may not stay
169+ /// in the narrow for reasons such as message moves or deletions,
170+ /// but that's fine for what this is used for.
171+ int ? get newMessageId => _newMessageId;
172+ int ? _newMessageId;
173+
148174 /// Whether [messages] and [items] represent the results of a fetch.
149175 ///
150176 /// This allows the UI to distinguish "still working on fetching messages"
@@ -409,6 +435,8 @@ mixin _MessageSequence {
409435 generation += 1 ;
410436 messages.clear ();
411437 middleMessage = 0 ;
438+ _oldMessageId = null ;
439+ _newMessageId = null ;
412440 outboxMessages.clear ();
413441 _haveOldest = false ;
414442 _haveNewest = false ;
@@ -815,9 +843,13 @@ class MessageListView with ChangeNotifier, _MessageSequence {
815843 }
816844
817845 /// Fetch messages, starting from scratch.
846+ ///
847+ /// This makes sure there is at least one non-muted message fetched; if any.
848+ /// It may do so my repeatedly calling [fetchOlder] and [fetchNewer] .
818849 Future <void > fetchInitial () async {
819850 assert (! fetched && ! haveOldest && ! haveNewest && ! busyFetchingMore);
820851 assert (messages.isEmpty && contents.isEmpty);
852+ assert (oldMessageId == null && newMessageId == null );
821853
822854 if (narrow case KeywordSearchNarrow (keyword: '' )) {
823855 // The server would reject an empty keyword search; skip the request.
@@ -841,6 +873,9 @@ class MessageListView with ChangeNotifier, _MessageSequence {
841873 );
842874 if (this .generation > generation) return ;
843875
876+ _oldMessageId = result.messages.firstOrNull? .id;
877+ _newMessageId = result.messages.lastOrNull? .id;
878+
844879 _adjustNarrowForTopicPermalink (result.messages.firstOrNull);
845880
846881 store.reconcileMessages (result.messages);
@@ -868,6 +903,12 @@ class MessageListView with ChangeNotifier, _MessageSequence {
868903 _syncOutboxMessagesFromStore ();
869904 }
870905
906+ while (messages.isEmpty && ! (haveOldest && haveNewest)) {
907+ await fetchOlder (partOfInitialFetch: true );
908+ if (messages.isNotEmpty) break ;
909+ await fetchNewer (partOfInitialFetch: true );
910+ }
911+
871912 _setStatus (FetchingStatus .idle, was: FetchingStatus .fetchInitial);
872913 }
873914
@@ -911,16 +952,18 @@ class MessageListView with ChangeNotifier, _MessageSequence {
911952 /// then this method does nothing and immediately returns.
912953 /// That makes this method suitable to call frequently, e.g. every frame,
913954 /// whenever it looks likely to be useful to have more messages.
914- Future <void > fetchOlder () async {
955+ Future <void > fetchOlder ({ bool partOfInitialFetch = false } ) async {
915956 if (haveOldest) return ;
916957 if (busyFetchingMore) return ;
917- assert (fetched);
918- assert (messages.isNotEmpty );
958+ assert (partOfInitialFetch || fetched);
959+ assert (oldMessageId != null );
919960 await _fetchMore (
920- anchor: NumericAnchor (messages[ 0 ].id ),
961+ anchor: NumericAnchor (oldMessageId ! ),
921962 numBefore: kMessageListFetchBatchSize,
922963 numAfter: 0 ,
964+ partOfInitialFetch: partOfInitialFetch,
923965 processResult: (result) {
966+ _oldMessageId = result.messages.firstOrNull? .id ?? oldMessageId;
924967 store.reconcileMessages (result.messages);
925968 store.recentSenders.handleMessages (result.messages); // TODO(#824)
926969
@@ -941,16 +984,18 @@ class MessageListView with ChangeNotifier, _MessageSequence {
941984 /// then this method does nothing and immediately returns.
942985 /// That makes this method suitable to call frequently, e.g. every frame,
943986 /// whenever it looks likely to be useful to have more messages.
944- Future <void > fetchNewer () async {
987+ Future <void > fetchNewer ({ bool partOfInitialFetch = false } ) async {
945988 if (haveNewest) return ;
946989 if (busyFetchingMore) return ;
947- assert (fetched);
948- assert (messages.isNotEmpty );
990+ assert (partOfInitialFetch || fetched);
991+ assert (newMessageId != null );
949992 await _fetchMore (
950- anchor: NumericAnchor (messages.last.id ),
993+ anchor: NumericAnchor (newMessageId ! ),
951994 numBefore: 0 ,
952995 numAfter: kMessageListFetchBatchSize,
996+ partOfInitialFetch: partOfInitialFetch,
953997 processResult: (result) {
998+ _newMessageId = result.messages.lastOrNull? .id ?? newMessageId;
954999 store.reconcileMessages (result.messages);
9551000 store.recentSenders.handleMessages (result.messages); // TODO(#824)
9561001
@@ -971,12 +1016,15 @@ class MessageListView with ChangeNotifier, _MessageSequence {
9711016 required Anchor anchor,
9721017 required int numBefore,
9731018 required int numAfter,
1019+ required bool partOfInitialFetch,
9741020 required void Function (GetMessagesResult ) processResult,
9751021 }) async {
9761022 assert (narrow is ! TopicNarrow
9771023 // We only intend to send "with" in [fetchInitial]; see there.
9781024 || (narrow as TopicNarrow ).with_ == null );
979- _setStatus (FetchingStatus .fetchingMore, was: FetchingStatus .idle);
1025+ if (! partOfInitialFetch) {
1026+ _setStatus (FetchingStatus .fetchingMore, was: FetchingStatus .idle);
1027+ }
9801028 final generation = this .generation;
9811029 bool hasFetchError = false ;
9821030 try {
@@ -998,7 +1046,7 @@ class MessageListView with ChangeNotifier, _MessageSequence {
9981046
9991047 processResult (result);
10001048 } finally {
1001- if (this .generation == generation) {
1049+ if (this .generation == generation && ! partOfInitialFetch ) {
10021050 if (hasFetchError) {
10031051 _setStatus (FetchingStatus .backoff, was: FetchingStatus .fetchingMore);
10041052 unawaited ((_fetchBackoffMachine ?? = BackoffMachine ())
0 commit comments