@@ -65,6 +65,9 @@ class MessageListHistoryStartItem extends MessageListItem {
65
65
///
66
66
/// This comprises much of the guts of [MessageListView] .
67
67
mixin _MessageSequence {
68
+ /// A sequence number for invalidating stale fetches.
69
+ int generation = 0 ;
70
+
68
71
/// The messages.
69
72
///
70
73
/// See also [contents] and [items] .
@@ -192,6 +195,17 @@ mixin _MessageSequence {
192
195
_reprocessAll ();
193
196
}
194
197
198
+ /// Reset all [_MessageSequence] data, and cancel any active fetches.
199
+ void _reset () {
200
+ generation += 1 ;
201
+ messages.clear ();
202
+ _fetched = false ;
203
+ _haveOldest = false ;
204
+ _fetchingOlder = false ;
205
+ contents.clear ();
206
+ items.clear ();
207
+ }
208
+
195
209
/// Redo all computations from scratch, based on [messages] .
196
210
void _recompute () {
197
211
assert (contents.length == messages.length);
@@ -396,12 +410,14 @@ class MessageListView with ChangeNotifier, _MessageSequence {
396
410
assert (! fetched && ! haveOldest && ! fetchingOlder);
397
411
assert (messages.isEmpty && contents.isEmpty);
398
412
// TODO schedule all this in another isolate
413
+ final generation = this .generation;
399
414
final result = await getMessages (store.connection,
400
415
narrow: narrow.apiEncode (),
401
416
anchor: AnchorCode .newest,
402
417
numBefore: kMessageListFetchBatchSize,
403
418
numAfter: 0 ,
404
419
);
420
+ if (this .generation > generation) return ;
405
421
store.reconcileMessages (result.messages);
406
422
for (final message in result.messages) {
407
423
if (_messageVisible (message)) {
@@ -423,6 +439,7 @@ class MessageListView with ChangeNotifier, _MessageSequence {
423
439
_fetchingOlder = true ;
424
440
_updateEndMarkers ();
425
441
notifyListeners ();
442
+ final generation = this .generation;
426
443
try {
427
444
final result = await getMessages (store.connection,
428
445
narrow: narrow.apiEncode (),
@@ -431,6 +448,7 @@ class MessageListView with ChangeNotifier, _MessageSequence {
431
448
numBefore: kMessageListFetchBatchSize,
432
449
numAfter: 0 ,
433
450
);
451
+ if (this .generation > generation) return ;
434
452
435
453
if (result.messages.isNotEmpty
436
454
&& result.messages.last.id == messages[0 ].id) {
@@ -447,9 +465,11 @@ class MessageListView with ChangeNotifier, _MessageSequence {
447
465
_insertAllMessages (0 , fetchedMessages);
448
466
_haveOldest = result.foundOldest;
449
467
} finally {
450
- _fetchingOlder = false ;
451
- _updateEndMarkers ();
452
- notifyListeners ();
468
+ if (this .generation == generation) {
469
+ _fetchingOlder = false ;
470
+ _updateEndMarkers ();
471
+ notifyListeners ();
472
+ }
453
473
}
454
474
}
455
475
@@ -485,6 +505,72 @@ class MessageListView with ChangeNotifier, _MessageSequence {
485
505
}
486
506
}
487
507
508
+ void _messagesMovedInternally (List <int > messageIds) {
509
+ for (final messageId in messageIds) {
510
+ if (_findMessageWithId (messageId) != - 1 ) {
511
+ _reprocessAll ();
512
+ notifyListeners ();
513
+ return ;
514
+ }
515
+ }
516
+ }
517
+
518
+ void _messagesMovedIntoNarrow () {
519
+ // If there are some messages we don't have in [MessageStore], and they
520
+ // occur later than the messages we have here, then we just have to
521
+ // re-fetch from scratch. That's always valid, so just do that always.
522
+ // TODO in cases where we do have data to do better, do better.
523
+ _reset ();
524
+ notifyListeners ();
525
+ fetchInitial ();
526
+ }
527
+
528
+ void _messagesMovedFromNarrow (List <int > messageIds) {
529
+ if (_removeMessagesById (messageIds)) {
530
+ notifyListeners ();
531
+ }
532
+ }
533
+
534
+ void messagesMoved ({
535
+ required int origStreamId,
536
+ required int newStreamId,
537
+ required String origTopic,
538
+ required String newTopic,
539
+ required List <int > messageIds,
540
+ }) {
541
+ switch (narrow) {
542
+ case DmNarrow ():
543
+ // DMs can't be moved (nor created by moves),
544
+ // so the messages weren't in this narrow and still aren't.
545
+ return ;
546
+
547
+ case CombinedFeedNarrow ():
548
+ // The messages were and remain in this narrow.
549
+ // TODO(#421): … except they may have become muted or not.
550
+ // We'll handle that at the same time as we handle muting itself changing.
551
+ // Recipient headers, and downstream of those, may change, though.
552
+ _messagesMovedInternally (messageIds);
553
+
554
+ case StreamNarrow (: final streamId):
555
+ switch ((origStreamId == streamId, newStreamId == streamId)) {
556
+ case (false , false ): return ;
557
+ case (true , true ): _messagesMovedInternally (messageIds);
558
+ case (false , true ): _messagesMovedIntoNarrow ();
559
+ case (true , false ): _messagesMovedFromNarrow (messageIds);
560
+ }
561
+
562
+ case TopicNarrow (: final streamId, : final topic):
563
+ final oldMatch = (origStreamId == streamId && origTopic == topic);
564
+ final newMatch = (newStreamId == streamId && newTopic == topic);
565
+ switch ((oldMatch, newMatch)) {
566
+ case (false , false ): return ;
567
+ case (true , true ): _messagesMovedInternally (messageIds);
568
+ case (false , true ): _messagesMovedIntoNarrow ();
569
+ case (true , false ): _messagesMovedFromNarrow (messageIds); // TODO handle propagateMode
570
+ }
571
+ }
572
+ }
573
+
488
574
// Repeal the `@protected` annotation that applies on the base implementation,
489
575
// so we can call this method from [MessageStoreImpl].
490
576
@override
0 commit comments