Skip to content

Commit e7708d6

Browse files
committed
model: Implement edit-message methods on MessageStore
Related: #126
1 parent 237859d commit e7708d6

File tree

3 files changed

+371
-0
lines changed

3 files changed

+371
-0
lines changed

lib/model/message.dart

+87
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,36 @@ 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 for the given message, if any, has failed.
40+
///
41+
/// Will be null if there is no current edit request.
42+
/// Will be false if the current request hasn't failed
43+
/// and the update-message event hasn't arrived.
44+
bool? getEditMessageErrorStatus(int messageId);
45+
46+
/// Edit a message's content, via a request to the server.
47+
///
48+
/// Should only be called when there is no current edit request for [messageId],
49+
/// i.e., [getEditMessageErrorStatus] returns null for [messageId].
50+
///
51+
/// See also:
52+
/// * [getEditMessageErrorStatus]
53+
/// * [takeFailedMessageEdit]
54+
void editMessage({required int messageId, required String newContent});
55+
56+
/// Forgets the failed edit request and returns the attempted new content.
57+
///
58+
/// Should only be called when there is a failed request,
59+
/// per [getEditMessageErrorStatus].
60+
String takeFailedMessageEdit(int messageId);
61+
}
62+
63+
class _EditMessageRequestStatus {
64+
_EditMessageRequestStatus({required this.hasError, required this.newContent});
65+
66+
bool hasError;
67+
final String newContent;
3868
}
3969

4070
class MessageStoreImpl extends PerAccountStoreBase with MessageStore {
@@ -132,6 +162,56 @@ class MessageStoreImpl extends PerAccountStoreBase with MessageStore {
132162
}
133163
}
134164

165+
@override
166+
bool? getEditMessageErrorStatus(int messageId) =>
167+
_editMessageRequests[messageId]?.hasError;
168+
169+
final Map<int, _EditMessageRequestStatus> _editMessageRequests = {};
170+
171+
@override
172+
void editMessage({
173+
required int messageId,
174+
required String newContent,
175+
}) async {
176+
if (_editMessageRequests.containsKey(messageId)) {
177+
throw StateError('an edit request is already in progress');
178+
}
179+
180+
_editMessageRequests[messageId] = _EditMessageRequestStatus(
181+
hasError: false, newContent: newContent);
182+
_notifyMessageListViewsForOneMessage(messageId);
183+
try {
184+
await updateMessage(connection, messageId: messageId, content: newContent);
185+
// On success, we'll clear the 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.newContent;
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);

lib/model/store.dart

+15
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,21 @@ class PerAccountStore extends PerAccountStoreBase with ChangeNotifier, EmojiStor
747747
// TODO(#649) notify [unreads] of the just-fetched messages
748748
// TODO(#650) notify [recentDmConversationsView] of the just-fetched messages
749749
}
750+
@override
751+
bool? getEditMessageErrorStatus(int messageId) {
752+
assert(!_disposed);
753+
return _messages.getEditMessageErrorStatus(messageId);
754+
}
755+
@override
756+
void editMessage({required int messageId, required String newContent}) {
757+
assert(!_disposed);
758+
return _messages.editMessage(messageId: messageId, newContent: newContent);
759+
}
760+
@override
761+
String takeFailedMessageEdit(int messageId) {
762+
assert(!_disposed);
763+
return _messages.takeFailedMessageEdit(messageId);
764+
}
750765

751766
@override
752767
Set<MessageListView> get debugMessageListViews => _messages.debugMessageListViews;

0 commit comments

Comments
 (0)