Skip to content

Commit 727b9d8

Browse files
committed
event: Handle submessage event for polls.
We could potentially avoid notifying listeners in some cases when handling submessage event, but it wouldn't be critically necessary. Signed-off-by: Zixuan James Li <[email protected]>
1 parent a85dbe4 commit 727b9d8

10 files changed

+493
-0
lines changed

lib/api/model/events.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:json_annotation/json_annotation.dart';
33
import '../../model/algorithms.dart';
44
import 'json.dart';
55
import 'model.dart';
6+
import 'submessage.dart';
67

78
part 'events.g.dart';
89

@@ -63,6 +64,7 @@ sealed class Event {
6364
case 'remove': return UpdateMessageFlagsRemoveEvent.fromJson(json);
6465
default: return UnexpectedEvent.fromJson(json);
6566
}
67+
case 'submessage': return SubmessageEvent.fromJson(json);
6668
case 'typing': return TypingEvent.fromJson(json);
6769
case 'reaction': return ReactionEvent.fromJson(json);
6870
case 'heartbeat': return HeartbeatEvent.fromJson(json);
@@ -876,6 +878,41 @@ class UpdateMessageFlagsMessageDetail {
876878
Map<String, dynamic> toJson() => _$UpdateMessageFlagsMessageDetailToJson(this);
877879
}
878880

881+
/// A Zulip event of type `submessage`: https://zulip.com/api/get-events#submessage
882+
@JsonSerializable(fieldRename: FieldRename.snake)
883+
class SubmessageEvent extends Event {
884+
@override
885+
@JsonKey(includeToJson: true)
886+
String get type => 'submessage';
887+
888+
@JsonKey(unknownEnumValue: SubmessageType.unknown)
889+
final SubmessageType msgType;
890+
/// [SubmessageData] encoded in JSON.
891+
// We cannot parse the String into one of the [SubmessageData] classes because
892+
// information from other submessages are required. Specifically, we need
893+
// the parsed [WidgetData] from the first [Message.submessages] of the
894+
// corresponding message.
895+
final String content;
896+
final int messageId;
897+
final int senderId;
898+
final int submessageId;
899+
900+
SubmessageEvent({
901+
required super.id,
902+
required this.msgType,
903+
required this.content,
904+
required this.messageId,
905+
required this.senderId,
906+
required this.submessageId,
907+
});
908+
909+
factory SubmessageEvent.fromJson(Map<String, dynamic> json) =>
910+
_$SubmessageEventFromJson(json);
911+
912+
@override
913+
Map<String, dynamic> toJson() => _$SubmessageEventToJson(this);
914+
}
915+
879916
/// A Zulip event of type `typing`:
880917
/// https://zulip.com/api/get-events#typing-start
881918
/// https://zulip.com/api/get-events#typing-stop

lib/api/model/events.g.dart

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/api/model/submessage.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'dart:convert';
33
import 'package:json_annotation/json_annotation.dart';
44

55
import '../../log.dart';
6+
import 'events.dart';
67

78
part 'submessage.g.dart';
89

@@ -402,6 +403,17 @@ class Poll {
402403
final Set<String> _existingOptionTexts = {};
403404
final Map<OptionKey, PollOption> _options = {};
404405

406+
void handleSubmessageEvent(SubmessageEvent event) {
407+
final PollEventSubmessage? pollEventSubmessage;
408+
try {
409+
pollEventSubmessage = PollEventSubmessage.fromJson(jsonDecode(event.content) as Map<String, Object?>);
410+
} catch (e) {
411+
assert(debugLog('Malformed submessage event data for poll: $e\n${jsonEncode(event)}')); // TODO(log)
412+
return;
413+
}
414+
applyEvent(event.senderId, pollEventSubmessage);
415+
}
416+
405417
void applyEvent(int senderId, PollEventSubmessage event) {
406418
switch (event) {
407419
case PollNewOptionEventSubmessage():

lib/model/message.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:convert';
2+
13
import '../api/model/events.dart';
24
import '../api/model/model.dart';
35
import '../log.dart';
@@ -302,4 +304,21 @@ class MessageStoreImpl with MessageStore {
302304
view.notifyListenersIfMessagePresent(event.messageId);
303305
}
304306
}
307+
308+
void handleSubmessageEvent(SubmessageEvent event) {
309+
final message = messages[event.messageId];
310+
if (message == null) return;
311+
312+
final poll = message.poll;
313+
if (poll == null) {
314+
assert(debugLog('Missing poll for submessage event:\n${jsonEncode(event)}')); // TODO(log)
315+
return;
316+
}
317+
318+
poll.handleSubmessageEvent(event);
319+
320+
for (final view in _messageListViews) {
321+
view.notifyListenersIfMessagePresent(event.messageId);
322+
}
323+
}
305324
}

lib/model/store.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,10 @@ class PerAccountStore extends ChangeNotifier with ChannelStore, MessageStore {
537537
_messages.handleUpdateMessageFlagsEvent(event);
538538
unreads.handleUpdateMessageFlagsEvent(event);
539539

540+
case SubmessageEvent():
541+
assert(debugLog("server event: submessage ${event.content}"));
542+
_messages.handleSubmessageEvent(event);
543+
540544
case TypingEvent():
541545
assert(debugLog("server event: typing/${event.op} ${event.messageType}"));
542546
typingStatus.handleTypingEvent(event);

test/api/model/model_checks.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:checks/checks.dart';
22
import 'package:zulip/api/model/model.dart';
3+
import 'package:zulip/api/model/submessage.dart';
34

45
extension UserChecks on Subject<User> {
56
Subject<int> get userId => has((x) => x.userId, 'userId');
@@ -39,6 +40,7 @@ extension MessageChecks on Subject<Message> {
3940
Subject<int> get senderId => has((e) => e.senderId, 'senderId');
4041
Subject<String> get senderRealmStr => has((e) => e.senderRealmStr, 'senderRealmStr');
4142
Subject<String> get topic => has((e) => e.topic, 'topic');
43+
Subject<Poll?> get poll => has((e) => e.poll, 'poll');
4244
Subject<int> get timestamp => has((e) => e.timestamp, 'timestamp');
4345
Subject<String> get type => has((e) => e.type, 'type');
4446
Subject<List<MessageFlag>> get flags => has((e) => e.flags, 'flags');

test/api/model/submessage_checks.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,13 @@ extension PollVoteEventChecks on Subject<PollVoteEventSubmessage> {
3737
Subject<String> get key => has((e) => e.key, 'key');
3838
Subject<PollVoteOp> get op => has((e) => e.op, 'op');
3939
}
40+
41+
extension PollChecks on Subject<Poll> {
42+
Subject<String> get question => has((e) => e.question, 'question');
43+
Subject<Iterable<PollOption>> get options => has((e) => e.options, 'options');
44+
}
45+
46+
extension PollOptionChecks on Subject<PollOption> {
47+
Subject<String> get text => has((e) => e.text, 'text');
48+
Subject<Set<int>> get voters => has((e) => e.voters, 'voters');
49+
}

test/api/model/submessage_test.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,8 @@ void main() {
114114
'key': PollEventSubmessage.optionKey(senderId: null, idx: 0)
115115
})).isA<PollVoteEventSubmessage>().op.equals(PollVoteOp.unknown);
116116
});
117+
118+
// Parsing polls with PollEventSubmessages are tested in
119+
// `test/model/message_test.dart` in the "new message with initial
120+
// submessages" and "handleSubmessageEvent" tests.
117121
}

test/example_data.dart

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'dart:convert';
12
import 'dart:math';
23

34
import 'package:zulip/api/model/events.dart';
@@ -343,6 +344,7 @@ StreamMessage streamMessage({
343344
List<Reaction>? reactions,
344345
int? timestamp,
345346
List<MessageFlag>? flags,
347+
List<Submessage>? submessages,
346348
}) {
347349
_checkPositive(id, 'message ID');
348350
final effectiveStream = stream ?? _stream(streamId: defaultStreamMessageStreamId);
@@ -362,6 +364,7 @@ StreamMessage streamMessage({
362364
'id': id ?? _nextMessageId(),
363365
'last_edit_timestamp': lastEditTimestamp,
364366
'subject': topic ?? 'example topic',
367+
'submessages': submessages ?? [],
365368
'timestamp': timestamp ?? 1678139636,
366369
'type': 'stream',
367370
}) as Map<String, dynamic>);
@@ -386,6 +389,7 @@ DmMessage dmMessage({
386389
int? lastEditTimestamp,
387390
int? timestamp,
388391
List<MessageFlag>? flags,
392+
List<Submessage>? submessages,
389393
}) {
390394
_checkPositive(id, 'message ID');
391395
assert(!to.any((user) => user.userId == from.userId));
@@ -401,6 +405,7 @@ DmMessage dmMessage({
401405
'id': id ?? _nextMessageId(),
402406
'last_edit_timestamp': lastEditTimestamp,
403407
'subject': '',
408+
'submessages': submessages ?? [],
404409
'timestamp': timestamp ?? 1678139636,
405410
'type': 'private',
406411
}) as Map<String, dynamic>);
@@ -414,6 +419,21 @@ PollWidgetData pollWidgetData({
414419
extraData: PollWidgetExtraData(question: question, options: options));
415420
}
416421

422+
Submessage submessage({
423+
SubmessageType? msgType,
424+
required Object? content,
425+
int? senderId,
426+
}) {
427+
return Submessage(
428+
msgType: msgType ?? SubmessageType.widget,
429+
content: jsonEncode(content),
430+
senderId: senderId ?? selfUser.userId,
431+
);
432+
}
433+
434+
PollOption pollOption({required String text, required Iterable<int> voters}) =>
435+
PollOption(text: text)..voters.addAll(voters);
436+
417437
////////////////////////////////////////////////////////////////
418438
// Aggregate data structures.
419439
//
@@ -627,6 +647,21 @@ UpdateMessageFlagsRemoveEvent updateMessageFlagsRemoveEvent(
627647
})));
628648
}
629649

650+
SubmessageEvent submessageEvent(
651+
int messageId,
652+
int senderId, {
653+
required SubmessageData? content,
654+
}) {
655+
return SubmessageEvent(
656+
id: 0,
657+
msgType: SubmessageType.widget,
658+
content: jsonEncode(content),
659+
messageId: messageId,
660+
senderId: senderId,
661+
submessageId: 100,
662+
);
663+
}
664+
630665
TypingEvent typingEvent(SendableNarrow narrow, TypingOp op, int senderId) {
631666
switch (narrow) {
632667
case TopicNarrow():

0 commit comments

Comments
 (0)