Skip to content

anchors 8/n: Let new messages accumulate in bottom sliver #1507

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
60 changes: 56 additions & 4 deletions lib/model/message_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,26 @@ mixin _MessageSequence {
/// A sequence number for invalidating stale fetches.
int generation = 0;

/// The messages.
/// The known messages in the list.
///
/// This may or may not represent all the message history that
/// conceptually belongs in this message list.
/// That information is expressed in [fetched] and [haveOldest].
///
/// See also [middleMessage], an index which divides this list
/// into a top slice and a bottom slice.
///
/// See also [contents] and [items].
final List<Message> messages = [];

/// An index into [messages] dividing it into a top slice and a bottom slice.
///
/// The indices 0 to before [middleMessage] are the top slice of [messages],
/// and the indices from [middleMessage] to the end are the bottom slice.
///
/// The corresponding item index is [middleItem].
int middleMessage = 0;

/// Whether [messages] and [items] represent the results of a fetch.
///
/// This allows the UI to distinguish "still working on fetching messages"
Expand Down Expand Up @@ -133,8 +148,23 @@ mixin _MessageSequence {
/// This information is completely derived from [messages] and
/// the flags [haveOldest], [fetchingOlder] and [fetchOlderCoolingDown].
/// It exists as an optimization, to memoize that computation.
///
/// See also [middleItem], an index which divides this list
/// into a top slice and a bottom slice.
final QueueList<MessageListItem> items = QueueList();

/// An index into [items] dividing it into a top slice and a bottom slice.
///
/// The indices 0 to before [middleItem] are the top slice of [items],
/// and the indices from [middleItem] to the end are the bottom slice.
///
/// The top and bottom slices of [items] correspond to
/// the top and bottom slices of [messages] respectively.
/// Either the bottom slices of both [items] and [messages] are empty,
/// or the first item in the bottom slice of [items] is a [MessageListMessageItem]
/// for the first message in the bottom slice of [messages].
int middleItem = 0;

int _findMessageWithId(int messageId) {
return binarySearchByKey(messages, messageId,
(message, messageId) => message.id.compareTo(messageId));
Expand Down Expand Up @@ -202,6 +232,7 @@ mixin _MessageSequence {
candidate++;
assert(contents.length == messages.length);
while (candidate < messages.length) {
if (candidate == middleMessage) middleMessage = target;
if (test(messages[candidate])) {
candidate++;
continue;
Expand All @@ -210,6 +241,7 @@ mixin _MessageSequence {
contents[target] = contents[candidate];
target++; candidate++;
}
if (candidate == middleMessage) middleMessage = target;
messages.length = target;
contents.length = target;
assert(contents.length == messages.length);
Expand All @@ -232,6 +264,13 @@ mixin _MessageSequence {
}
if (messagesToRemoveById.isEmpty) return false;

if (middleMessage == messages.length) {
middleMessage -= messagesToRemoveById.length;
} else {
final middleMessageId = messages[middleMessage].id;
middleMessage -= messagesToRemoveById
.where((id) => id < middleMessageId).length;
}
assert(contents.length == messages.length);
messages.removeWhere((message) => messagesToRemoveById.contains(message.id));
contents.removeWhere((content) => contentToRemove.contains(content));
Expand All @@ -246,25 +285,31 @@ mixin _MessageSequence {
// On a Pixel 5, a batch of 100 messages takes ~15-20ms in _insertAllMessages.
// (Before that, ~2-5ms in jsonDecode and 0ms in fromJson,
// so skip worrying about those steps.)
final oldLength = messages.length;
assert(contents.length == messages.length);
messages.insertAll(index, toInsert);
contents.insertAll(index, toInsert.map(
(message) => _parseMessageContent(message)));
assert(contents.length == messages.length);
if (index <= middleMessage) {
middleMessage += messages.length - oldLength;
}
_reprocessAll();
}

/// Reset all [_MessageSequence] data, and cancel any active fetches.
void _reset() {
generation += 1;
messages.clear();
middleMessage = 0;
_fetched = false;
_haveOldest = false;
_fetchingOlder = false;
_fetchOlderCoolingDown = false;
_fetchOlderCooldownBackoffMachine = null;
contents.clear();
items.clear();
middleItem = 0;
}

/// Redo all computations from scratch, based on [messages].
Expand Down Expand Up @@ -304,6 +349,7 @@ mixin _MessageSequence {
canShareSender = (prevMessageItem.message.senderId == message.senderId);
}
}
if (index == middleMessage) middleItem = items.length;
items.add(MessageListMessageItem(message, content,
showSender: !canShareSender, isLastInBlock: true));
}
Expand All @@ -314,6 +360,7 @@ mixin _MessageSequence {
for (var i = 0; i < messages.length; i++) {
_processMessage(i);
}
if (middleMessage == messages.length) middleItem = items.length;
}
}

Expand Down Expand Up @@ -453,13 +500,18 @@ class MessageListView with ChangeNotifier, _MessageSequence {
allowEmptyTopicName: true,
);
if (this.generation > generation) return;

_adjustNarrowForTopicPermalink(result.messages.firstOrNull);

store.reconcileMessages(result.messages);
store.recentSenders.handleMessages(result.messages); // TODO(#824)

// We'll make the bottom slice start at the last visible message, if any.
for (final message in result.messages) {
if (_messageVisible(message)) {
_addMessage(message);
}
if (!_messageVisible(message)) continue;
middleMessage = messages.length;
_addMessage(message);
// Now [middleMessage] is the last message (the one just added).
}
_fetched = true;
_haveOldest = result.foundOldest;
Expand Down
5 changes: 2 additions & 3 deletions lib/widgets/message_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -573,10 +573,9 @@ class _MessageListState extends State<MessageList> with PerAccountStoreAwareStat
// The list has two slivers: a top sliver growing upward,
// and a bottom sliver growing downward.
// Each sliver has some of the items from `model.items`.
const maxBottomItems = 1;
final totalItems = model.items.length;
final bottomItems = totalItems <= maxBottomItems ? totalItems : maxBottomItems;
final topItems = totalItems - bottomItems;
final topItems = model.middleItem;
final bottomItems = totalItems - topItems;

// The top sliver has its child 0 as the item just before the
// sliver boundary, child 1 as the item before that, and so on.
Expand Down
Loading