Skip to content

Commit 3d9e342

Browse files
Finished the job but idk why the tests are failing.Have to troubleshoot
1 parent efcf201 commit 3d9e342

File tree

2 files changed

+296
-45
lines changed

2 files changed

+296
-45
lines changed

lib/model/message_list.dart

+169-14
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,13 @@ class MessageListLoadingItem extends MessageListItem {
5656
final MessageListDirection direction;
5757

5858
const MessageListLoadingItem(this.direction);
59+
5960
}
6061

61-
enum MessageListDirection { older }
62+
enum MessageListDirection {
63+
older,
64+
newer
65+
}
6266

6367
/// Indicates we've reached the oldest message in the narrow.
6468
class MessageListHistoryStartItem extends MessageListItem {
@@ -85,9 +89,7 @@ mixin _MessageSequence {
8589
bool _fetched = false;
8690

8791
/// Whether we know we have the oldest messages for this narrow.
88-
///
89-
/// (Currently we always have the newest messages for the narrow,
90-
/// once [fetched] is true, because we start from the newest.)
92+
9193
bool get haveOldest => _haveOldest;
9294
bool _haveOldest = false;
9395

@@ -118,6 +120,38 @@ mixin _MessageSequence {
118120

119121
BackoffMachine? _fetchOlderCooldownBackoffMachine;
120122

123+
124+
/// Whether we are currently fetching the next batch of newer messages.
125+
///
126+
/// When this is true, [fetchNewer] is a no-op.
127+
/// That method is called frequently by Flutter's scrolling logic,
128+
/// and this field helps us avoid spamming the same request just to get
129+
/// the same response each time.
130+
///
131+
/// See also [fetchNewerCoolingDown].
132+
bool get fetchingNewer => _fetchingNewer;
133+
bool _fetchingNewer = false;
134+
135+
/// Whether [fetchNewer] had a request error recently.
136+
///
137+
/// When this is true, [fetchNewer] is a no-op.
138+
/// That method is called frequently by Flutter's scrolling logic,
139+
/// and this field helps us avoid spamming the same request and getting
140+
/// the same error each time.
141+
///
142+
/// "Recently" is decided by a [BackoffMachine] that resets
143+
/// when a [fetchNewer] request succeeds.
144+
///
145+
/// See also [fetchingNewer].
146+
bool get fetchNewerCoolingDown => _fetchNewerCoolingDown;
147+
bool _fetchNewerCoolingDown = false;
148+
149+
BackoffMachine? _fetchNewerCooldownBackoffMachine;
150+
151+
/// Whether we know we have the newest messages for this narrow.
152+
bool get haveNewest => _haveNewest;
153+
bool _haveNewest = false;
154+
121155
/// The parsed message contents, as a list parallel to [messages].
122156
///
123157
/// The i'th element is the result of parsing the i'th element of [messages].
@@ -133,7 +167,8 @@ mixin _MessageSequence {
133167
/// before, between, or after the messages.
134168
///
135169
/// This information is completely derived from [messages] and
136-
/// the flags [haveOldest], [fetchingOlder] and [fetchOlderCoolingDown].
170+
/// the flags [haveOldest], [fetchingOlder] and [fetchOlderCoolingDown]
171+
/// and [haveNewest], [fetchingNewer] and [fetchNewerCoolingDown].
137172
/// It exists as an optimization, to memoize that computation.
138173
final QueueList<MessageListItem> items = QueueList();
139174

@@ -152,6 +187,7 @@ mixin _MessageSequence {
152187
case MessageListLoadingItem():
153188
switch (item.direction) {
154189
case MessageListDirection.older: return -1;
190+
case MessageListDirection.newer: return 1;
155191
}
156192
case MessageListRecipientHeaderItem(:var message):
157193
case MessageListDateSeparatorItem(:var message):
@@ -271,6 +307,10 @@ mixin _MessageSequence {
271307
_fetchOlderCooldownBackoffMachine = null;
272308
contents.clear();
273309
items.clear();
310+
_fetchingNewer = false;
311+
_fetchNewerCoolingDown = false;
312+
_fetchNewerCooldownBackoffMachine = null;
313+
_haveNewest = false;
274314
}
275315

276316
/// Redo all computations from scratch, based on [messages].
@@ -318,24 +358,53 @@ mixin _MessageSequence {
318358
void _updateEndMarkers() {
319359
assert(fetched);
320360
assert(!(fetchingOlder && fetchOlderCoolingDown));
361+
assert(!(fetchingNewer && fetchNewerCoolingDown));
362+
321363
final effectiveFetchingOlder = fetchingOlder || fetchOlderCoolingDown;
364+
final effectiveFetchingNewer = fetchingNewer || fetchNewerCoolingDown;
365+
322366
assert(!(effectiveFetchingOlder && haveOldest));
367+
assert(!(effectiveFetchingNewer && haveNewest));
368+
369+
// Handle start marker (older messages)
323370
final startMarker = switch ((effectiveFetchingOlder, haveOldest)) {
324371
(true, _) => const MessageListLoadingItem(MessageListDirection.older),
325372
(_, true) => const MessageListHistoryStartItem(),
326373
(_, _) => null,
327374
};
375+
376+
// Handle end marker (newer messages)
377+
final endMarker = switch ((effectiveFetchingNewer, haveNewest)) {
378+
(true, _) => const MessageListLoadingItem(MessageListDirection.newer),
379+
(_, _) => null, // No "history end" marker needed since we start from newest
380+
};
381+
328382
final hasStartMarker = switch (items.firstOrNull) {
329383
MessageListLoadingItem() => true,
330384
MessageListHistoryStartItem() => true,
331385
_ => false,
332386
};
387+
388+
final hasEndMarker = switch (items.lastOrNull) {
389+
MessageListLoadingItem() => true,
390+
_ => false,
391+
};
392+
393+
// Update start marker
333394
switch ((startMarker != null, hasStartMarker)) {
334395
case (true, true): items[0] = startMarker!;
335396
case (true, _ ): items.addFirst(startMarker!);
336397
case (_, true): items.removeFirst();
337398
case (_, _ ): break;
338399
}
400+
401+
// Update end marker
402+
switch ((endMarker != null, hasEndMarker)) {
403+
case (true, true): items[items.length - 1] = endMarker!;
404+
case (true, _ ): items.add(endMarker!);
405+
case (_, true): items.removeLast();
406+
case (_, _ ): break;
407+
}
339408
}
340409

341410
/// Recompute [items] from scratch, based on [messages], [contents], and flags.
@@ -408,16 +477,20 @@ bool _sameDay(DateTime date1, DateTime date2) {
408477
/// * Add listeners with [addListener].
409478
/// * Fetch messages with [fetchInitial]. When the fetch completes, this object
410479
/// will notify its listeners (as it will any other time the data changes.)
411-
/// * Fetch more messages as needed with [fetchOlder].
480+
/// * Fetch more messages as needed with [fetchOlder] or [fetchNewer].
412481
/// * On reassemble, call [reassemble].
413482
/// * When the object will no longer be used, call [dispose] to free
414483
/// resources on the [PerAccountStore].
415484
class MessageListView with ChangeNotifier, _MessageSequence {
416-
MessageListView._({required this.store, required this.narrow});
485+
MessageListView._({required this.store, required this.narrow, this.anchorMessageId});
417486

487+
// Anchor message ID is used to fetch messages from a specific point in the list.
488+
// It is set when the user navigates to a message list page with a specific anchor message.
489+
int? anchorMessageId;
490+
int? get anchorIndex => anchorMessageId != null ? findItemWithMessageId(anchorMessageId!) : null;
418491
factory MessageListView.init(
419-
{required PerAccountStore store, required Narrow narrow}) {
420-
final view = MessageListView._(store: store, narrow: narrow);
492+
{required PerAccountStore store, required Narrow narrow, int? anchorMessageId}) {
493+
final view = MessageListView._(store: store, narrow: narrow, anchorMessageId: anchorMessageId);
421494
store.registerMessageList(view);
422495
return view;
423496
}
@@ -496,20 +569,30 @@ class MessageListView with ChangeNotifier, _MessageSequence {
496569
}
497570
}
498571

572+
573+
499574
/// Fetch messages, starting from scratch.
500575
Future<void> fetchInitial() async {
501576
// TODO(#80): fetch from anchor firstUnread, instead of newest
502-
// TODO(#82): fetch from a given message ID as anchor
503-
assert(!fetched && !haveOldest && !fetchingOlder && !fetchOlderCoolingDown);
577+
578+
assert(!fetched && !haveOldest && !fetchingOlder && !fetchOlderCoolingDown && !fetchingNewer && !fetchNewerCoolingDown && !haveNewest);
504579
assert(messages.isEmpty && contents.isEmpty);
505580
// TODO schedule all this in another isolate
506581
final generation = this.generation;
507582
final result = await getMessages(store.connection,
508583
narrow: narrow.apiEncode(),
509-
anchor: AnchorCode.newest,
510-
numBefore: kMessageListFetchBatchSize,
511-
numAfter: 0,
584+
anchor: anchorMessageId != null
585+
? NumericAnchor(anchorMessageId!)
586+
: AnchorCode.newest,
587+
numBefore: anchorMessageId != null
588+
? kMessageListFetchBatchSize ~/ 2 // Fetch messages before and after anchor
589+
: kMessageListFetchBatchSize, // Fetch only older messages when no anchor
590+
numAfter: anchorMessageId != null
591+
? kMessageListFetchBatchSize ~/2 // Fetch messages before and after anchor
592+
: 0, // Don't fetch newer messages when no anchor
512593
);
594+
anchorMessageId ??= result.messages.last.id;
595+
513596
if (this.generation > generation) return;
514597
store.reconcileMessages(result.messages);
515598
store.recentSenders.handleMessages(result.messages); // TODO(#824)
@@ -520,10 +603,12 @@ class MessageListView with ChangeNotifier, _MessageSequence {
520603
}
521604
_fetched = true;
522605
_haveOldest = result.foundOldest;
606+
_haveNewest = result.foundNewest;
523607
_updateEndMarkers();
524608
notifyListeners();
525609
}
526610

611+
527612
/// Fetch the next batch of older messages, if applicable.
528613
Future<void> fetchOlder() async {
529614
if (haveOldest) return;
@@ -589,6 +674,76 @@ class MessageListView with ChangeNotifier, _MessageSequence {
589674
}
590675
}
591676

677+
/// Fetch the next batch of newer messages, if applicable.
678+
Future<void> fetchNewer() async {
679+
if (haveNewest) return;
680+
if (fetchingNewer) return;
681+
if (fetchNewerCoolingDown) return;
682+
assert(fetched);
683+
assert(messages.isNotEmpty);
684+
685+
_fetchingNewer = true;
686+
_updateEndMarkers();
687+
notifyListeners();
688+
689+
final generation = this.generation;
690+
bool hasFetchError = false;
691+
692+
try {
693+
final GetMessagesResult result;
694+
try {
695+
result = await getMessages(store.connection,
696+
narrow: narrow.apiEncode(),
697+
anchor: NumericAnchor(messages.last.id),
698+
includeAnchor: false,
699+
numBefore: 0,
700+
numAfter: kMessageListFetchBatchSize,
701+
);
702+
} catch (e) {
703+
hasFetchError = true;
704+
rethrow;
705+
}
706+
if (this.generation > generation) return;
707+
708+
if (result.messages.isNotEmpty
709+
&& result.messages.first.id == messages.last.id) {
710+
// TODO(server-6): includeAnchor should make this impossible
711+
result.messages.removeAt(0);
712+
}
713+
714+
store.reconcileMessages(result.messages);
715+
store.recentSenders.handleMessages(result.messages);
716+
717+
final fetchedMessages = _allMessagesVisible
718+
? result.messages
719+
: result.messages.where(_messageVisible);
720+
721+
_insertAllMessages(messages.length, fetchedMessages);
722+
723+
_haveNewest = result.foundNewest;
724+
725+
} finally {
726+
if (this.generation == generation) {
727+
_fetchingNewer = false;
728+
if (hasFetchError) {
729+
assert(!fetchNewerCoolingDown);
730+
_fetchNewerCoolingDown = true;
731+
unawaited((_fetchNewerCooldownBackoffMachine ??= BackoffMachine())
732+
.wait().then((_) {
733+
if (this.generation != generation) return;
734+
_fetchNewerCoolingDown = false;
735+
_updateEndMarkers();
736+
notifyListeners();
737+
}));
738+
} else {
739+
_fetchNewerCooldownBackoffMachine = null;
740+
}
741+
_updateEndMarkers();
742+
notifyListeners();
743+
}
744+
}
745+
}
746+
592747
void handleUserTopicEvent(UserTopicEvent event) {
593748
switch (_canAffectVisibility(event)) {
594749
case VisibilityEffect.none:

0 commit comments

Comments
 (0)