Skip to content

Commit b1730ae

Browse files
committed
msglist: Jump, not scroll, to end when it might be far
When the message list is truly far back in history -- for example, at first unread in the combined feed or a busy channel, for a user who has some old unreads going back months and years -- trying to scroll smoothly to the bottom is hopeless. The only way to get to the newest messages in any reasonable amount of time is to jump there. So, do that.
1 parent 48abb5c commit b1730ae

File tree

4 files changed

+53
-4
lines changed

4 files changed

+53
-4
lines changed

lib/model/message_list.dart

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ class MessageListView with ChangeNotifier, _MessageSequence {
479479
/// which might be made internally by this class in order to
480480
/// fetch the messages from scratch, e.g. after certain events.
481481
Anchor get anchor => _anchor;
482-
final Anchor _anchor;
482+
Anchor _anchor;
483483

484484
void _register() {
485485
store.registerMessageList(this);
@@ -756,6 +756,20 @@ class MessageListView with ChangeNotifier, _MessageSequence {
756756
}
757757
}
758758

759+
/// Reset this view to start from the newest messages.
760+
///
761+
/// This will set [anchor] to [AnchorCode.newest],
762+
/// and cause messages to be re-fetched from scratch.
763+
void jumpToEnd() {
764+
assert(fetched);
765+
assert(!haveNewest);
766+
assert(anchor != AnchorCode.newest);
767+
_anchor = AnchorCode.newest;
768+
_reset();
769+
notifyListeners();
770+
fetchInitial();
771+
}
772+
759773
/// Add [outboxMessage] if it belongs to the view.
760774
void addOutboxMessage(OutboxMessage outboxMessage) {
761775
// TODO(#1441) implement this

lib/widgets/message_list.dart

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,7 @@ class _MessageListState extends State<MessageList> with PerAccountStoreAwareStat
554554
// redirected us to the new location of the operand message ID.
555555
widget.onNarrowChanged(model.narrow);
556556
}
557+
// TODO when model reset, reset scroll
557558
setState(() {
558559
// The actual state lives in the [MessageListView] model.
559560
// This method was called because that just changed.
@@ -638,6 +639,7 @@ class _MessageListState extends State<MessageList> with PerAccountStoreAwareStat
638639
// MessageList's dartdoc.
639640
child: SafeArea(
640641
child: ScrollToBottomButton(
642+
model: model,
641643
scrollController: scrollController,
642644
visible: _scrollToBottomVisible))),
643645
])))));
@@ -837,13 +839,40 @@ class _MessageListLoadingMore extends StatelessWidget {
837839
}
838840

839841
class ScrollToBottomButton extends StatelessWidget {
840-
const ScrollToBottomButton({super.key, required this.scrollController, required this.visible});
842+
const ScrollToBottomButton({
843+
super.key,
844+
required this.model,
845+
required this.scrollController,
846+
required this.visible,
847+
});
841848

842-
final ValueNotifier<bool> visible;
849+
final MessageListView model;
843850
final MessageListScrollController scrollController;
851+
final ValueNotifier<bool> visible;
844852

845853
void _scrollToBottom() {
846-
scrollController.position.scrollToEnd();
854+
if (model.haveNewest) {
855+
// Scrolling smoothly from here to the bottom won't require any requests
856+
// to the server.
857+
// It also probably isn't *that* far away: the user must have scrolled
858+
// here from there (or from near enough that a fetch reached there),
859+
// so scrolling back there -- at top speed -- shouldn't take too long.
860+
// Go for it.
861+
scrollController.position.scrollToEnd();
862+
} else {
863+
// This message list doesn't have the messages for the bottom of history.
864+
// There could be quite a lot of history between here and there --
865+
// for example, at first unread in the combined feed or a busy channel,
866+
// for a user who has some old unreads going back months and years.
867+
// In that case trying to scroll smoothly to the bottom is hopeless.
868+
//
869+
// Given that there were at least 100 messages between this message list's
870+
// initial anchor and the end of history (or else `fetchInitial` would
871+
// have reached the end at the outset), that situation is very likely.
872+
// Even if the end is close by, it's at least one fetch away.
873+
// Instead of scrolling, jump to the end, which is always just one fetch.
874+
model.jumpToEnd();
875+
}
847876
}
848877

849878
@override

test/model/message_list_test.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,8 @@ void main() {
566566
});
567567
});
568568

569+
// TODO(#1569): test jumpToEnd
570+
569571
group('MessageEvent', () {
570572
test('in narrow', () async {
571573
final stream = eg.stream();

test/widgets/message_list_test.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,8 @@ void main() {
375375

376376
group('fetch initial batch of messages', () {
377377
// TODO(#1569): test effect of initAnchorMessageId
378+
// TODO(#1569): test that after jumpToEnd, then new store causing new fetch,
379+
// new post-jump anchor prevails over initAnchorMessageId
378380

379381
group('topic permalink', () {
380382
final someStream = eg.stream();
@@ -668,6 +670,8 @@ void main() {
668670
check(isButtonVisible(tester)).equals(false);
669671
});
670672

673+
// TODO(#1569): test choice of jumpToEnd vs. scrollToEnd
674+
671675
testWidgets('scrolls at reasonable, constant speed', (tester) async {
672676
const maxSpeed = 8000.0;
673677
const distance = 40000.0;

0 commit comments

Comments
 (0)