Skip to content

Commit 6c3d733

Browse files
committed
msglist: Handle updated events in MessageListView (zulip#118).
Processes an UpdateMessageEvent and hands it off to the MessageListView to update, if the message is visible in the MessageListView. This completes the changes required for issue zulip#118.
1 parent 2465701 commit 6c3d733

File tree

4 files changed

+176
-5
lines changed

4 files changed

+176
-5
lines changed

lib/api/model/model.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -250,13 +250,13 @@ class Subscription {
250250
sealed class Message {
251251
final String? avatarUrl;
252252
final String client;
253-
final String content;
253+
String content;
254254
final String contentType;
255255

256256
// final List<MessageEditHistory> editHistory; // TODO handle
257257
final int id;
258-
final bool isMeMessage;
259-
final int? lastEditTimestamp;
258+
bool isMeMessage;
259+
int? lastEditTimestamp;
260260

261261
// final List<Reaction> reactions; // TODO handle
262262
final int recipientId;
@@ -271,7 +271,7 @@ sealed class Message {
271271

272272
// final List<TopicLink> topicLinks; // TODO handle
273273
// final string type; // handled by runtime type of object
274-
final List<String> flags; // TODO enum
274+
List<String> flags; // TODO enum
275275
final String? matchContent;
276276
final String? matchSubject;
277277

lib/model/message_list.dart

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import 'package:collection/collection.dart';
12
import 'package:flutter/foundation.dart';
23

4+
import '../api/model/events.dart';
35
import '../api/model/model.dart';
46
import '../api/route/messages.dart';
57
import 'content.dart';
@@ -86,6 +88,68 @@ class MessageListView extends ChangeNotifier {
8688
notifyListeners();
8789
}
8890

91+
applyChangesToMessage(UpdateMessageEvent event, Message message) {
92+
if (event.renderingOnly != null && event.renderingOnly == true) {
93+
//TODO update inline preview
94+
return;
95+
}
96+
97+
if (!event.flags.equals(message.flags)) {
98+
//TODO Possibly a new @alert on this message. Update alerts.
99+
message.flags = event.flags;
100+
}
101+
102+
if (event.renderedContent != null) {
103+
104+
message.content = event.renderedContent as String;
105+
}
106+
107+
if (event.editTimestamp != null) {
108+
message.lastEditTimestamp = event.editTimestamp;
109+
}
110+
111+
if (event.isMeMessage != null) {
112+
message.isMeMessage = event.isMeMessage as bool;
113+
}
114+
115+
}
116+
117+
///This is almost directly copied from package:collection/algorithms.dart.
118+
///The way that package was set up doesn't allow us to search
119+
///for a message ID among a bunch of message objects - this is a quick
120+
///modification of that method to work here for us.
121+
int findMessageWithId(int messageId) {
122+
var min = 0;
123+
var max = messages.length;
124+
125+
while (min < max) {
126+
var mid = min + ((max - min) >> 1);
127+
Message message = messages[mid];
128+
var comp = message.id.compareTo(messageId);
129+
if (comp == 0) return mid;
130+
if (comp < 0) {
131+
min = mid + 1;
132+
} else {
133+
max = mid;
134+
}
135+
}
136+
return -1;
137+
}
138+
139+
void maybeUpdateMessage(UpdateMessageEvent event) {
140+
int idx = findMessageWithId(event.messageId);
141+
142+
if (idx == -1) {
143+
return;
144+
}
145+
146+
Message message = messages[idx];
147+
applyChangesToMessage(event, message);
148+
149+
contents[idx] = parseContent(message.content);
150+
notifyListeners();
151+
}
152+
89153
/// Called when the app is reassembled during debugging, e.g. for hot reload.
90154
///
91155
/// This will redo from scratch any computations we can, such as parsing

lib/model/store.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,9 @@ class PerAccountStore extends ChangeNotifier {
272272
}
273273
} else if (event is UpdateMessageEvent) {
274274
assert(debugLog("server event: update_message ${event.messageId}"));
275-
// TODO handle
275+
for (final view in _messageListViews) {
276+
view.maybeUpdateMessage(event);
277+
}
276278
} else if (event is DeleteMessageEvent) {
277279
assert(debugLog("server event: delete_message ${event.messageIds}"));
278280
// TODO handle

test/model/message_list_test.dart

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import 'package:checks/checks.dart';
2+
import 'package:test/scaffolding.dart';
3+
import 'package:zulip/api/model/events.dart';
4+
import 'package:zulip/api/model/model.dart';
5+
import 'package:zulip/api/route/messages.dart';
6+
import 'package:zulip/model/message_list.dart';
7+
import 'package:zulip/model/narrow.dart';
8+
import 'package:zulip/model/store.dart';
9+
import '../api/fake_api.dart';
10+
import '../model/binding.dart';
11+
import '../model/test_store.dart';
12+
import '../example_data.dart' as eg;
13+
14+
const int userId = 1;
15+
const int streamId = 2;
16+
17+
Future<PerAccountStore> setupStore(ZulipStream stream) async {
18+
await TestZulipBinding.instance.globalStore.add(eg.selfAccount, eg.initialSnapshot());
19+
PerAccountStore store = await TestZulipBinding.instance.globalStore.perAccount(eg.selfAccount.id);
20+
store.addUser(eg.user(userId: userId));
21+
store.addStream(stream);
22+
return store;
23+
}
24+
25+
void main() {
26+
TestZulipBinding.ensureInitialized();
27+
const narrow = StreamNarrow(streamId);
28+
29+
30+
group('update message tests', () {
31+
32+
test('find message in message list returns index of message', () async {
33+
final ZulipStream stream = eg.stream(streamId: streamId);
34+
PerAccountStore store = await setupStore(stream);
35+
MessageListView messageList = MessageListView.init(store: store, narrow: narrow);
36+
37+
final connection = store.connection as FakeApiConnection;
38+
Message m1 = eg.streamMessage(id: 792, stream: stream);
39+
Message m2 = eg.streamMessage(id: 793, stream: stream);
40+
Message m3 = eg.streamMessage(id: 794, stream: stream);
41+
42+
connection.prepare(json: GetMessagesResult(
43+
anchor: m1.id,
44+
foundNewest: true,
45+
foundOldest: true,
46+
foundAnchor: true,
47+
historyLimited: false,
48+
messages: [m1, m2, m3],
49+
).toJson());
50+
51+
await messageList.fetch();
52+
53+
check(messageList.messages.length).equals(3);
54+
55+
int idx = messageList.findMessageWithId(793);
56+
check(idx).equals(1);
57+
58+
idx = messageList.findMessageWithId(999);
59+
check(idx).equals(-1);
60+
});
61+
62+
test('update events are correctly applied to message', () async {
63+
final ZulipStream stream = eg.stream(streamId: streamId);
64+
PerAccountStore store = await setupStore(stream);
65+
MessageListView messageList = MessageListView.init(store: store, narrow: narrow);
66+
67+
final connection = store.connection as FakeApiConnection;
68+
String oldContent = "<p>Hello, world</p>";
69+
String newContent = "<p>Hello, edited</p>";
70+
int newTimestamp = 99999;
71+
72+
Message m = eg.streamMessage(id: 243, stream: stream, content: oldContent);
73+
connection.prepare(json: GetMessagesResult(
74+
anchor: m.id,
75+
foundNewest: true,
76+
foundOldest: true,
77+
foundAnchor: true,
78+
historyLimited: false,
79+
messages: [m],
80+
).toJson());
81+
82+
await messageList.fetch();
83+
84+
UpdateMessageEvent updateEvent = UpdateMessageEvent(
85+
id: 1,
86+
messageId: m.id,
87+
messageIds: [m.id],
88+
flags: m.flags,
89+
renderedContent: newContent,
90+
editTimestamp: newTimestamp
91+
);
92+
93+
Message oldMessage = messageList.messages[0];
94+
check(oldMessage.content).equals(oldContent);
95+
96+
messageList.maybeUpdateMessage(updateEvent);
97+
98+
Message updatedMessage = messageList.messages[0];
99+
100+
check(updatedMessage.content).equals(newContent);
101+
check(updatedMessage.lastEditTimestamp).equals(newTimestamp);
102+
103+
});
104+
});
105+
}

0 commit comments

Comments
 (0)