-
Notifications
You must be signed in to change notification settings - Fork 374
msglist: Fix the first and third buggy behaviors in #1798! #2034
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
base: main
Are you sure you want to change the base?
Changes from all commits
7d73858
2e82af2
522df03
2be8ead
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -519,7 +519,8 @@ class MessageStoreImpl extends HasChannelStore with MessageStore, _OutboxMessage | |
| content: newContent, | ||
| prevContentSha256: sha256.convert(utf8.encode(originalRawContent)).toString()); | ||
| // On success, we'll clear the status from _editMessageRequests | ||
| // when we get the event. | ||
| // when we get the event (or below, if in an unsubscribed channel). | ||
| if (_disposed) return; | ||
| } catch (e) { | ||
| // TODO(log) if e is something unexpected | ||
|
|
||
|
|
@@ -536,6 +537,15 @@ class MessageStoreImpl extends HasChannelStore with MessageStore, _OutboxMessage | |
| _notifyMessageListViewsForOneMessage(messageId); | ||
| rethrow; | ||
| } | ||
|
|
||
| final message = messages[messageId]; | ||
| if (message is StreamMessage && subscriptions[message.streamId] == null) { | ||
| // The message is in an unsubscribed channel. We don't expect an event | ||
| // (see "third buggy behavior" in #1798) but we know the edit request | ||
| // succeeded, so, clear the pending-edit state. | ||
| _editMessageRequests.remove(messageId); | ||
| _notifyMessageListViewsForOneMessage(messageId); | ||
| } | ||
| } | ||
|
|
||
| @override | ||
|
|
@@ -889,13 +899,18 @@ const kSendMessageOfferRestoreWaitPeriod = Duration(seconds: 10); // TODO(#1441 | |
| /// timed out. not finished when | ||
| /// wait period timed out. | ||
| /// | ||
| /// Event received. | ||
| /// (any state) ─────────────────► (delete) | ||
| /// Event received, or [sendMessage] | ||
| /// request succeeds and we're sending to | ||
| /// an unsubscribed channel. | ||
| /// (any state) ───────────────────────────────────────► (delete) | ||
| /// ``` | ||
| /// | ||
| /// During its lifecycle, it is guaranteed that the outbox message is deleted | ||
| /// as soon a message event with a matching [MessageEvent.localMessageId] | ||
| /// arrives. | ||
| /// If we're sending to an unsubscribed channel, we don't expect an event | ||
| /// (see "third buggy behavior" in #1798) so in that case | ||
| /// the outbox message is deleted when the [sendMessage] request succeeds. | ||
| enum OutboxMessageState { | ||
| /// The [sendMessage] HTTP request has started but the resulting | ||
| /// [MessageEvent] hasn't arrived, and nor has the request failed. In this | ||
|
|
@@ -1148,6 +1163,19 @@ mixin _OutboxMessageStore on HasChannelStore { | |
| // The message event already arrived; nothing to do. | ||
| return; | ||
| } | ||
|
|
||
| if (destination is StreamDestination && subscriptions[destination.streamId] == null) { | ||
| // We don't expect an event (we're sending to an unsubscribed channel); | ||
| // clear the loading spinner. | ||
| _outboxMessages.remove(localMessageId); | ||
|
Comment on lines
+1167
to
+1170
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm. This means we'll remove not only the loading spinner, but also the local-echo version of the message itself, right? That seems potentially unhelpful. Though I guess this might be simultaneous with reloading the affected message lists from scratch? In that case the user wouldn't see a state where the message appears to have vanished. |
||
| _outboxMessageDebounceTimers.remove(localMessageId)?.cancel(); | ||
| _outboxMessageWaitPeriodTimers.remove(localMessageId)?.cancel(); | ||
| for (final view in _messageListViews) { | ||
| view.notifyListenersIfOutboxMessagePresent(localMessageId); | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| // The send request succeeded, so the message was definitely sent. | ||
| // Cancel the timer that would have had us start presuming that the | ||
| // send might have failed. | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1324,13 +1324,14 @@ class _SendButtonState extends State<_SendButton> { | |||||||||||||||
| return; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| final store = PerAccountStoreWidget.of(context); | ||||||||||||||||
| PerAccountStore store = PerAccountStoreWidget.of(context); | ||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I see, the point is we'll mutate this variable after the async gap, looking up the new value it should have. nit: Let's move this line to just before the one place this version is used. (And it can go back to |
||||||||||||||||
| final content = controller.content.textNormalized; | ||||||||||||||||
|
|
||||||||||||||||
| controller.content.clear(); | ||||||||||||||||
|
|
||||||||||||||||
| try { | ||||||||||||||||
| await store.sendMessage(destination: widget.getDestination(), content: content); | ||||||||||||||||
| if (!mounted) return; | ||||||||||||||||
| } on ApiRequestException catch (e) { | ||||||||||||||||
| if (!mounted) return; | ||||||||||||||||
| final zulipLocalizations = ZulipLocalizations.of(context); | ||||||||||||||||
|
|
@@ -1343,6 +1344,20 @@ class _SendButtonState extends State<_SendButton> { | |||||||||||||||
| message: message); | ||||||||||||||||
| return; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| store = PerAccountStoreWidget.of(context); | ||||||||||||||||
| final destination = widget.getDestination(); | ||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The point is this is the destination we just sent to, right? Perhaps save that earlier in a variable, vs. recomputing here, to make that clearer. |
||||||||||||||||
| if ( | ||||||||||||||||
| destination is StreamDestination | ||||||||||||||||
| && store.subscriptions[destination.streamId] == null | ||||||||||||||||
| ) { | ||||||||||||||||
| // We don't get new-message events for unsubscribed channels, | ||||||||||||||||
| // but we can refresh the view when a send-message request succeeds, | ||||||||||||||||
|
Comment on lines
+1353
to
+1355
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this could use a line setting the context a bit more broadly:
Suggested change
|
||||||||||||||||
| // so the user will at least see their own messages without having to | ||||||||||||||||
| // exit and re-enter. See the "first buggy behavior" in | ||||||||||||||||
| // https://github.com/zulip/zulip-flutter/issues/1798 . | ||||||||||||||||
| MessageListPage.ancestorOf(context).refresh(AnchorCode.newest); | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| @override | ||||||||||||||||
|
|
@@ -1854,7 +1869,7 @@ class _EditMessageBannerTrailing extends StatelessWidget { | |||||||||||||||
| // disappears, which may be long after the banner disappears.) | ||||||||||||||||
| final pageContext = PageRoot.contextOf(context); | ||||||||||||||||
|
|
||||||||||||||||
| final store = PerAccountStoreWidget.of(pageContext); | ||||||||||||||||
| PerAccountStore store = PerAccountStoreWidget.of(pageContext); | ||||||||||||||||
| final controller = composeBoxState.controller; | ||||||||||||||||
| if (controller is! EditMessageComposeBoxController) return; // TODO(log) | ||||||||||||||||
| final zulipLocalizations = ZulipLocalizations.of(pageContext); | ||||||||||||||||
|
|
@@ -1885,6 +1900,7 @@ class _EditMessageBannerTrailing extends StatelessWidget { | |||||||||||||||
| messageId: messageId, | ||||||||||||||||
| originalRawContent: originalRawContent, | ||||||||||||||||
| newContent: newContent); | ||||||||||||||||
| if (!pageContext.mounted) return; | ||||||||||||||||
| } on ApiRequestException catch (e) { | ||||||||||||||||
| if (!pageContext.mounted) return; | ||||||||||||||||
| final zulipLocalizations = ZulipLocalizations.of(pageContext); | ||||||||||||||||
|
|
@@ -1897,6 +1913,18 @@ class _EditMessageBannerTrailing extends StatelessWidget { | |||||||||||||||
| message: message); | ||||||||||||||||
| return; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| store = PerAccountStoreWidget.of(pageContext); | ||||||||||||||||
| final messageListPageState = MessageListPage.ancestorOf(pageContext); | ||||||||||||||||
| final narrow = messageListPageState.narrow; | ||||||||||||||||
| if (narrow is ChannelNarrow && store.subscriptions[narrow.streamId] == null) { | ||||||||||||||||
| // We don't get edit-message events for unsubscribed channels, | ||||||||||||||||
| // but we can refresh the view when an edit-message request succeeds, | ||||||||||||||||
| // so the user will at least see their updated message without having to | ||||||||||||||||
| // exit and re-enter. See the "first buggy behavior" in | ||||||||||||||||
| // https://github.com/zulip/zulip-flutter/issues/1798 . | ||||||||||||||||
| messageListPageState.refresh(NumericAnchor(messageId)); | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| @override | ||||||||||||||||
|
|
||||||||||||||||
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: In general with these changes, I worry somewhat about making it harder to understand the main case. This can help a bit here:
In particular that makes it clear up front that "event received" is a complete description of one way this transition can happen, and there aren't further caveats about it coming later.