@@ -35,6 +35,37 @@ mixin MessageStore {
3535 /// All [Message] objects in the resulting list will be present in
3636 /// [this.messages] .
3737 void reconcileMessages (List <Message > messages);
38+
39+ /// Whether the current edit request, if any, has failed.
40+ ///
41+ /// Will be null if there is no current edit request
42+ /// or the message was deleted.
43+ /// Will be false if the current request hasn't failed
44+ /// and the update-message event hasn't arrived.
45+ bool ? getEditMessageErrorStatus (int messageId);
46+
47+ /// Edits a message's content.
48+ ///
49+ /// Should only be called when there is no current edit request for [messageId] ,
50+ /// i.e., [getEditMessageErrorStatus] returns null for [messageId] .
51+ ///
52+ /// See also:
53+ /// * [getEditMessageErrorStatus]
54+ /// * [takeFailedMessageEdit]
55+ void editMessage ({required int messageId, required String content});
56+
57+ /// Forgets the failed edit request and returns the attempted new content.
58+ ///
59+ /// Should only be called when there is a failed request,
60+ /// per [getEditMessageErrorStatus] .
61+ String ? takeFailedMessageEdit (int messageId);
62+ }
63+
64+ class _EditMessageRequestStatus {
65+ _EditMessageRequestStatus ({required this .hasError, required this .content});
66+
67+ bool hasError;
68+ final String content;
3869}
3970
4071class MessageStoreImpl extends PerAccountStoreBase with MessageStore {
@@ -132,6 +163,55 @@ class MessageStoreImpl extends PerAccountStoreBase with MessageStore {
132163 }
133164 }
134165
166+ @override
167+ bool ? getEditMessageErrorStatus (int messageId) =>
168+ _editMessageRequests[messageId]? .hasError;
169+ final Map <int , _EditMessageRequestStatus > _editMessageRequests = {};
170+
171+ @override
172+ void editMessage ({
173+ required int messageId,
174+ required String content,
175+ }) async {
176+ if (_editMessageRequests.containsKey (messageId)) {
177+ throw StateError ('message ID already in editMessageRequests' );
178+ }
179+
180+ _editMessageRequests[messageId] = _EditMessageRequestStatus (
181+ hasError: false , content: content);
182+ _notifyMessageListViewsForOneMessage (messageId);
183+ try {
184+ await updateMessage (connection, messageId: messageId, content: content);
185+ // On success, we'll clear `status` from editMessageRequests
186+ // when we get the event.
187+ } catch (e) {
188+ // TODO(log) if e is something unexpected
189+
190+ final status = _editMessageRequests[messageId];
191+ if (status == null ) {
192+ // The event actually arrived before this request failed
193+ // (can happen with network issues).
194+ // Or, the message was deleted.
195+ return ;
196+ }
197+ status.hasError = true ;
198+ _notifyMessageListViewsForOneMessage (messageId);
199+ }
200+ }
201+
202+ @override
203+ String ? takeFailedMessageEdit (int messageId) {
204+ final status = _editMessageRequests.remove (messageId);
205+ _notifyMessageListViewsForOneMessage (messageId);
206+ if (status == null ) {
207+ throw StateError ('called takeFailedMessageEdit, but no edit' );
208+ }
209+ if (! status.hasError) {
210+ throw StateError ("called takeFailedMessageEdit, but edit hasn't failed" );
211+ }
212+ return status.content;
213+ }
214+
135215 void handleUserTopicEvent (UserTopicEvent event) {
136216 for (final view in _messageListViews) {
137217 view.handleUserTopicEvent (event);
@@ -183,6 +263,12 @@ class MessageStoreImpl extends PerAccountStoreBase with MessageStore {
183263 // The message is guaranteed to be edited.
184264 // See also: https://zulip.com/api/get-events#update_message
185265 message.editState = MessageEditState .edited;
266+
267+ // Clear the edit-message progress feedback.
268+ // This makes a rare bug where we might clear the feedback too early,
269+ // if the user raced with themself to edit the same message
270+ // from multiple clients.
271+ _editMessageRequests.remove (message.id);
186272 }
187273 if (event.renderedContent != null ) {
188274 assert (message.contentType == 'text/html' ,
@@ -245,6 +331,7 @@ class MessageStoreImpl extends PerAccountStoreBase with MessageStore {
245331 void handleDeleteMessageEvent (DeleteMessageEvent event) {
246332 for (final messageId in event.messageIds) {
247333 messages.remove (messageId);
334+ _editMessageRequests.remove (messageId);
248335 }
249336 for (final view in _messageListViews) {
250337 view.handleDeleteMessageEvent (event);
0 commit comments