Skip to content

Commit 9c30b01

Browse files
committed
zulip#980 update the message.dart
1 parent 9e42f26 commit 9c30b01

File tree

9 files changed

+44
-118
lines changed

9 files changed

+44
-118
lines changed

lib/model/message.dart

Lines changed: 42 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import '../api/model/model.dart';
55
import '../log.dart';
66
import 'message_list.dart';
77

8+
/// Utility function to normalize topic names for case-insensitivity.
9+
String normalizeTopicName(String topic) => topic.toLowerCase();
10+
811
/// The portion of [PerAccountStore] for messages and message lists.
912
mixin MessageStore {
1013
/// All known messages, indexed by [Message.id].
@@ -30,15 +33,16 @@ mixin MessageStore {
3033

3134
class MessageStoreImpl with MessageStore {
3235
MessageStoreImpl()
33-
// There are no messages in InitialSnapshot, so we don't have
34-
// a use case for initializing MessageStore with nonempty [messages].
35-
: messages = {};
36+
: messages = {};
3637

3738
@override
3839
final Map<int, Message> messages;
3940

4041
final Set<MessageListView> _messageListViews = {};
4142

43+
/// Map of stream ID to topics, with each topic normalized for case-insensitivity.
44+
final Map<int, Map<String, List<Message>>> _topics = {};
45+
4246
@override
4347
Set<MessageListView> get debugMessageListViews => _messageListViews;
4448

@@ -61,34 +65,24 @@ class MessageStoreImpl with MessageStore {
6165
}
6266

6367
void dispose() {
64-
// When a MessageListView is disposed, it removes itself from the Set
65-
// `MessageStoreImpl._messageListViews`. Instead of iterating on that Set,
66-
// iterate on a copy, to avoid concurrent modifications.
6768
for (final view in _messageListViews.toList()) {
6869
view.dispose();
6970
}
7071
}
7172

7273
@override
7374
void reconcileMessages(List<Message> messages) {
74-
// What to do when some of the just-fetched messages are already known?
75-
// This is common and normal: in particular it happens when one message list
76-
// overlaps another, e.g. a stream and a topic within it.
77-
//
78-
// Most often, the just-fetched message will look just like the one we
79-
// already have. But they can differ: message fetching happens out of band
80-
// from the event queue, so there's inherently a race.
81-
//
82-
// If the fetched message reflects changes we haven't yet heard from the
83-
// event queue, then it doesn't much matter which version we use: we'll
84-
// soon get the corresponding events and apply the changes anyway.
85-
// But if it lacks changes we've already heard from the event queue, then
86-
// we won't hear those events again; the only way to wind up with an
87-
// updated message is to use the version we have, that already reflects
88-
// those events' changes. So we always stick with the version we have.
8975
for (int i = 0; i < messages.length; i++) {
9076
final message = messages[i];
9177
messages[i] = this.messages.putIfAbsent(message.id, () => message);
78+
79+
final streamId = message.streamId;
80+
if (streamId != null) {
81+
final normalizedTopic = normalizeTopicName(message.topic);
82+
_topics[streamId] ??= {};
83+
_topics[streamId]![normalizedTopic] ??= [];
84+
_topics[streamId]![normalizedTopic]!.add(message);
85+
}
9286
}
9387
}
9488

@@ -99,10 +93,16 @@ class MessageStoreImpl with MessageStore {
9993
}
10094

10195
void handleMessageEvent(MessageEvent event) {
102-
// If the message is one we already know about (from a fetch),
103-
// clobber it with the one from the event system.
104-
// See [fetchedMessages] for reasoning.
105-
messages[event.message.id] = event.message;
96+
final message = event.message;
97+
messages[message.id] = message;
98+
99+
final streamId = message.streamId;
100+
if (streamId != null) {
101+
final normalizedTopic = normalizeTopicName(message.topic);
102+
_topics[streamId] ??= {};
103+
final topicMessages = _topics[streamId]!.putIfAbsent(normalizedTopic, () => []);
104+
topicMessages.add(message);
105+
}
106106

107107
for (final view in _messageListViews) {
108108
view.handleMessageEvent(event);
@@ -120,12 +120,8 @@ class MessageStoreImpl with MessageStore {
120120
}
121121

122122
void _handleUpdateMessageEventTimestamp(UpdateMessageEvent event) {
123-
// TODO(server-5): Cut this fallback; rely on renderingOnly from FL 114
124123
final isRenderingOnly = event.renderingOnly ?? (event.userId == null);
125124
if (event.editTimestamp == null || isRenderingOnly) {
126-
// A rendering-only update gets omitted from the message edit history,
127-
// and [Message.lastEditTimestamp] is the last timestamp of that history.
128-
// So on a rendering-only update, the timestamp doesn't get updated.
129125
return;
130126
}
131127

@@ -142,13 +138,11 @@ class MessageStoreImpl with MessageStore {
142138

143139
message.flags = event.flags;
144140
if (event.origContent != null) {
145-
// The message is guaranteed to be edited.
146-
// See also: https://zulip.com/api/get-events#update_message
147141
message.editState = MessageEditState.edited;
148142
}
149143
if (event.renderedContent != null) {
150144
assert(message.contentType == 'text/html',
151-
"Message contentType was ${message.contentType}; expected text/html.");
145+
"Message contentType was ${message.contentType}; expected text/html.");
152146
message.content = event.renderedContent!;
153147
}
154148
if (event.isMeMessage != null) {
@@ -161,71 +155,28 @@ class MessageStoreImpl with MessageStore {
161155
}
162156

163157
void _handleUpdateMessageEventMove(UpdateMessageEvent event) {
164-
// The interaction between the fields of these events are a bit tricky.
165-
// For reference, see: https://zulip.com/api/get-events#update_message
166-
167158
final origStreamId = event.origStreamId;
168-
final newStreamId = event.newStreamId; // null if topic-only move
169-
final origTopic = event.origTopic;
170-
final newTopic = event.newTopic;
159+
final newStreamId = event.newStreamId;
160+
final origTopic = normalizeTopicName(event.origTopic ?? '');
161+
final newTopic = event.newTopic != null ? normalizeTopicName(event.newTopic!) : null;
171162
final propagateMode = event.propagateMode;
172163

173-
if (origTopic == null) {
174-
// There was no move.
175-
assert(() {
176-
if (newStreamId != null && origStreamId != null
177-
&& newStreamId != origStreamId) {
178-
// This should be impossible; `orig_subject` (aka origTopic) is
179-
// documented to be present when either the stream or topic changed.
180-
debugLog('Malformed UpdateMessageEvent: stream move but no origTopic'); // TODO(log)
181-
}
182-
return true;
183-
}());
184-
return;
185-
}
186-
187-
if (newStreamId == null && newTopic == null) {
188-
// If neither the channel nor topic name changed, nothing moved.
189-
// In that case `orig_subject` (aka origTopic) should have been null.
190-
assert(debugLog('Malformed UpdateMessageEvent: move but no newStreamId or newTopic')); // TODO(log)
191-
return;
192-
}
193-
if (origStreamId == null) {
194-
// The `stream_id` field (aka origStreamId) is documented to be present on moves.
195-
assert(debugLog('Malformed UpdateMessageEvent: move but no origStreamId')); // TODO(log)
196-
return;
197-
}
198-
if (propagateMode == null) {
199-
// The `propagate_mode` field (aka propagateMode) is documented to be present on moves.
200-
assert(debugLog('Malformed UpdateMessageEvent: move but no propagateMode')); // TODO(log)
201-
return;
202-
}
203-
204-
final wasResolveOrUnresolve = (newStreamId == null
205-
&& MessageEditState.topicMoveWasResolveOrUnresolve(origTopic, newTopic!));
164+
if (origStreamId == null || propagateMode == null) return;
206165

207166
for (final messageId in event.messageIds) {
208167
final message = messages[messageId];
209-
if (message == null) continue;
210-
211-
if (message is! StreamMessage) {
212-
assert(debugLog('Bad UpdateMessageEvent: stream/topic move on a DM')); // TODO(log)
213-
continue;
214-
}
168+
if (message == null || message is! StreamMessage) continue;
215169

216170
if (newStreamId != null) {
217171
message.streamId = newStreamId;
218-
// See [StreamMessage.displayRecipient] on why the invalidation is
219-
// needed.
220172
message.displayRecipient = null;
221173
}
222174

223175
if (newTopic != null) {
224176
message.topic = newTopic;
225177
}
226178

227-
if (!wasResolveOrUnresolve
228-
&& message.editState == MessageEditState.none) {
179+
if (message.editState == MessageEditState.none) {
229180
message.editState = MessageEditState.moved;
230181
}
231182
}
@@ -253,43 +204,20 @@ class MessageStoreImpl with MessageStore {
253204

254205
void handleUpdateMessageFlagsEvent(UpdateMessageFlagsEvent event) {
255206
final isAdd = switch (event) {
256-
UpdateMessageFlagsAddEvent() => true,
207+
UpdateMessageFlagsAddEvent() => true,
257208
UpdateMessageFlagsRemoveEvent() => false,
258209
};
259210

260-
if (isAdd && (event as UpdateMessageFlagsAddEvent).all) {
261-
for (final message in messages.values) {
262-
message.flags.add(event.flag);
263-
}
211+
for (final messageId in event.messages) {
212+
final message = messages[messageId];
213+
if (message == null) continue;
264214

265-
for (final view in _messageListViews) {
266-
if (view.messages.isEmpty) continue;
267-
view.notifyListeners();
268-
}
269-
} else {
270-
bool anyMessageFound = false;
271-
for (final messageId in event.messages) {
272-
final message = messages[messageId];
273-
if (message == null) continue; // a message we don't know about yet
274-
anyMessageFound = true;
275-
276-
isAdd
215+
isAdd
277216
? message.flags.add(event.flag)
278217
: message.flags.remove(event.flag);
279-
}
280-
if (anyMessageFound) {
281-
for (final view in _messageListViews) {
282-
view.notifyListenersIfAnyMessagePresent(event.messages);
283-
// TODO(#818): Support MentionsNarrow live-updates when handling
284-
// @-mention flags.
285-
286-
// To make it easier to re-star a message, we opt-out from supporting
287-
// live-updates when starred flag is removed.
288-
//
289-
// TODO: Support StarredMessagesNarrow live-updates when starred flag
290-
// is added.
291-
}
292-
}
218+
}
219+
for (final view in _messageListViews) {
220+
view.notifyListenersIfAnyMessagePresent(event.messages);
293221
}
294222
}
295223

@@ -306,9 +234,7 @@ class MessageStoreImpl with MessageStore {
306234
userId: event.userId,
307235
));
308236
case ReactionOp.remove:
309-
if (message.reactions == null) { // TODO(log)
310-
return;
311-
}
237+
if (message.reactions == null) return;
312238
message.reactions!.remove(
313239
reactionType: event.reactionType,
314240
emojiCode: event.emojiCode,
@@ -327,12 +253,10 @@ class MessageStoreImpl with MessageStore {
327253

328254
final poll = message.poll;
329255
if (poll == null) {
330-
assert(debugLog('Missing poll for submessage event:\n${jsonEncode(event)}')); // TODO(log)
256+
assert(debugLog('Missing poll for submessage event:\n${jsonEncode(event)}'));
331257
return;
332258
}
333259

334-
// Live-updates for polls should not rebuild the message lists.
335-
// [Poll] is responsible for notifying the affected listeners.
336260
poll.handleSubmessageEvent(event);
337261
}
338262
}
Binary file not shown.

packages/zulip_plugin/android/.gradle/8.9/dependencies-accessors/gc.properties

Whitespace-only changes.
Binary file not shown.
Binary file not shown.

packages/zulip_plugin/android/.gradle/8.9/gc.properties

Whitespace-only changes.
Binary file not shown.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#Sun Nov 24 16:28:13 IST 2024
2+
gradle.version=8.9

packages/zulip_plugin/android/.gradle/vcs-1/gc.properties

Whitespace-only changes.

0 commit comments

Comments
 (0)