-
Notifications
You must be signed in to change notification settings - Fork 308
local echo (6.75/7): Revised commit for local echo (sans retrieving failed outbox content) #1538
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
Conversation
Thanks! I won't get to reading this until I'm back after next week. The title's numbering collides with #1535; I saw the title and initially thought this was a new version of that PR. Perhaps this is 6.75/7? 😛 |
Oh! Indeed.
Yes, that's how I named the branch and how I meant to name the PR. 😛 |
f12e04f
to
109562b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I've read through all the non-test changes and part of the tests. Generally this looks good; comments below.
lib/model/message_list.dart
Outdated
messages.clear(); | ||
outboxMessages.clear(); | ||
middleMessage = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: keep messages
and middleMessage
together (like they are in their declarations)
messages.clear(); | |
outboxMessages.clear(); | |
middleMessage = 0; | |
messages.clear(); | |
middleMessage = 0; | |
outboxMessages.clear(); |
lib/model/message_list.dart
Outdated
while (items.isNotEmpty && items.last is! MessageListMessageItem) { | ||
items.removeLast(); | ||
} | ||
assert(items.none((e) => e is MessageListOutboxMessageItem)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will loop through the entire list of items, which may be long with a lot of message history.
Let's avoid that kind of expensive check even in debug builds of the app — the message list already feels somewhat sluggish in debug mode. Instead we rely on the tests and their checkInvariants
function to confirm these invariants are satisfied.
lib/model/message_list.dart
Outdated
// We'll make the bottom slice start at the last visible message, if any. | ||
_removeOutboxMessageItems(); | ||
for (final message in result.messages) { | ||
if (!_messageVisible(message)) continue; | ||
middleMessage = messages.length; | ||
_addMessage(message); | ||
// Now [middleMessage] is the last message (the one just added). | ||
} | ||
_reprocessOutboxMessages(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this logic will have outboxMessages
get filled in before the fetch even completes. That feels kind of odd. This is a view-model for the message list, and what we actually show in the message list in that state is just a loading indicator and no messages.
And in the future with #82, we won't necessarily want them even after the fetch, depending on the value that haveNewest
ends up with after the fetch. (Just like you pointed out at #1515 (comment) for new actual messages.)
So how about we have _syncOutboxMessages
get called only here in the fetch-result handler, rather than before the fetch? Then in #1515 we can straightforwardly make it conditional on haveNewest
, and do the same in fetchNewer
.
Probably that's best done as a separate commit on top, to facilitate merging the main commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense; I agree.
Probably that's best done as a separate commit on top, to facilitate merging the main commit.
Hmm, since #1515 is already merged, the main commit will need to check haveNewest
before adding outbox messages to items
.
lib/model/message_list.dart
Outdated
bool removed = _removeOutboxMessagesWhere((message) => | ||
message is StreamOutboxMessage | ||
&& message.conversation.streamId == event.streamId | ||
&& message.conversation.topic == event.topicName); | ||
|
||
removed |= _removeMessagesWhere((message) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: handle the normal messages first, since they come earlier in the list
lib/model/message_list.dart
Outdated
for (final outboxMessage in store.outboxMessages.values) { | ||
if (_shouldAddOutboxMessage( | ||
outboxMessage, | ||
wasUnmuted: outboxMessage is StreamOutboxMessage | ||
&& outboxMessage.conversation.streamId == event.streamId |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this whole stanza goes away if we let fetchInitial
take care of syncing in the outbox messages when appropriate.
lib/model/message_list.dart
Outdated
_addMessage(message); | ||
if (event.localMessageId != null) { | ||
final localMessageId = int.parse(event.localMessageId!); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
int.parse
should always use explicit radix
checkState().equals(OutboxMessageState.hidden); | ||
checkNotNotified(); | ||
|
||
async.elapse(kLocalEchoDebounceDuration); | ||
checkState().equals(OutboxMessageState.waiting); | ||
checkNotifiedOnce(); | ||
|
||
await receiveMessage(eg.dmMessage(from: eg.selfUser, to: [eg.otherUser])); | ||
check(store.outboxMessages).isEmpty(); | ||
checkNotifiedOnce(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add all these checks as a prep commit? Then if there are any that change with the main change (or if there are none), that will be more visible.
test/model/message_list_test.dart
Outdated
// the bottom slice of `model.messages` is empty | ||
check(model.items).length.equals(model.middleItem); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this check is really about validating the behavior of middleItem
, not items
// the bottom slice of `model.messages` is empty | |
check(model.items).length.equals(model.middleItem); | |
check(model.middleItem).equals(model.items.length); |
test/model/message_list_test.dart
Outdated
check(model.items.sublist(model.middleItem)) | ||
..first.isA<MessageListOutboxMessageItem>() | ||
.message.identicalTo(model.outboxMessages.first) | ||
..every((item) => item.not((it) => it.isA<MessageListMessageItem>())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This "is not MessageListMessageItem" check is redundant with the "is MessageListOutboxMessageItem" check just above, given the overall structure of items
: all items for outbox-messages come after all items for normal messages. And that latter structure was verified in the big for (int j = 0; j < allMessages.length; j++)
loop above.
So this every
can be dropped. And then the .sublist
plus .first
can be simplified down.
109562b
to
c7f1bea
Compare
Thanks for the review! Revision pushed. This revision postpones copying |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Looks great; small comments just on the tests.
/// because outbox messages are considered newer than regular messages. | ||
/// | ||
/// This does not call [notifyListeners]. | ||
void _syncOutboxMessagesFromStore() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, I like this name change, too (adding "from store").
test/model/message_list_test.dart
Outdated
check(model.items[model.middleItem]) | ||
.isA<MessageListOutboxMessageItem>() | ||
.message.identicalTo(model.outboxMessages.first); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can make this a bit more visibly parallel to the normal-message case just below it:
check(model.items[model.middleItem]) | |
.isA<MessageListOutboxMessageItem>() | |
.message.identicalTo(model.outboxMessages.first); | |
check(model.items[model.middleItem]).isA<MessageListOutboxMessageItem>() | |
.message.identicalTo(model.outboxMessages.first); |
test/model/message_list_test.dart
Outdated
check(model) | ||
..fetched.isFalse() | ||
..outboxMessages.isEmpty(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This second check is implied by the first one plus an invariant, right?
Hmm, we could make that invariant more explicitly checked, though, in checkInvariants:
if (!model.fetched) {
check(model)
..messages.isEmpty()
+ ..outboxMessages.isEmpty()
… I guess the existing test explicitly checks messages
is empty, even though that's similarly redundant, because it's helpful for parallelism with the steps above and below. So this check makes sense to include for the same reason. For that same parallelism, though, it's probably clearest to leave it as a separate check
call that comes after the checkHasMessageIds
, rather than consolidate with the check here on fetched
.
test/model/message_list_test.dart
Outdated
connection.prepare(json: newestResult(foundOldest: false, | ||
messages: [eg.streamMessage(stream: stream, topic: 'topic')]).toJson()); | ||
final fetchFuture = model.fetchNewer(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit for realism: use newerResult
(the anchor
will be different)
c7f1bea
to
d0f454d
Compare
Thanks for the review! Revision pushed. |
With comments on additional notifyListeners calls we expect when we start having the message list actually show the local echo in its various states.
This adds some overhead on message-event handling, linear in the number of outbox messages in a view. We rely on that number being small. We add outboxMessages as a list independent from messages on _MessageSequence. Because outbox messages are not rendered (the raw content is shown as plain text), we leave the 1-1 relationship between `messages` and `contents` unchanged. When computing `items`, we now start to look at `outboxMessages` as well, with the guarantee that the items related to outbox messages always come after those for other messages. Look for places that call `_processOutboxMessage(int index)` for references, and the changes to `checkInvariants` on how this affects the message list invariants. This implements minimal support to display outbox message message item widgets in the message list, without indicators for theirs states. Retrieving content from failed sent requests and the full UI are implemented in a later commit. Co-authored-by: Chris Bobbe <[email protected]>
Thanks! Looks good; merging. |
d0f454d
to
11f05dc
Compare
Here's a revised version of a substantive commit from #1453:
9c0b955 msglist: Add and manage outbox message objects in message list view
That just leaves one more substantive commit from that PR:
1fa9f24 msglist: Support retrieving failed outbox message content