Skip to content

Commit 9b35f98

Browse files
committed
model: Implement edit-message methods on MessageStore
Related: #126
1 parent 29cbf10 commit 9b35f98

File tree

3 files changed

+372
-0
lines changed

3 files changed

+372
-0
lines changed

lib/model/message.dart

+87
Original file line numberDiff line numberDiff line change
@@ -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

4071
class 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);

lib/model/store.dart

+18
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,24 @@ class PerAccountStore extends PerAccountStoreBase with ChangeNotifier, EmojiStor
910910
return _messages.sendMessage(destination: destination, content: content);
911911
}
912912

913+
@override
914+
bool? getEditMessageErrorStatus(int messageId) {
915+
assert(!_disposed);
916+
return _messages.getEditMessageErrorStatus(messageId);
917+
}
918+
919+
@override
920+
void editMessage({required int messageId, required String content}) {
921+
assert(!_disposed);
922+
return _messages.editMessage(messageId: messageId, content: content);
923+
}
924+
925+
@override
926+
String? takeFailedMessageEdit(int messageId) {
927+
assert(!_disposed);
928+
return _messages.takeFailedMessageEdit(messageId);
929+
}
930+
913931
static List<CustomProfileField> _sortCustomProfileFields(List<CustomProfileField> initialCustomProfileFields) {
914932
// TODO(server): The realm-wide field objects have an `order` property,
915933
// but the actual API appears to be that the fields should be shown in

0 commit comments

Comments
 (0)