@@ -18,6 +18,11 @@ class Submessage {
18
18
19
19
@JsonKey (unknownEnumValue: SubmessageType .unknown)
20
20
final SubmessageType msgType;
21
+ /// [SubmessageData] encoded in JSON.
22
+ // We cannot parse the String into one of the [SubmessageData] classes because
23
+ // information from other submessages are required. Specifically, we need:
24
+ // * the index of this submessage in [Message.submessages];
25
+ // * the [WidgetType] of the first [Message.submessages].
21
26
final String content;
22
27
// final int messageId; // ignored; redundant with [Message.id]
23
28
final int senderId;
@@ -34,3 +39,229 @@ enum SubmessageType {
34
39
widget,
35
40
unknown,
36
41
}
42
+
43
+ sealed class SubmessageData {}
44
+
45
+ /// The data encoded in a submessage to make the message a Zulip widget.
46
+ ///
47
+ /// Expected from the first [Submessage.content] in the "submessages" field on
48
+ /// the message when there is an widget.
49
+ ///
50
+ /// See https://zulip.readthedocs.io/en/latest/subsystems/widgets.html
51
+ sealed class WidgetData extends SubmessageData {
52
+ WidgetType get widgetType;
53
+
54
+ WidgetData ();
55
+
56
+ factory WidgetData .fromJson (Object ? json) {
57
+ final map = json as Map <String , Object ?>;
58
+ final rawWidgetType = map['widget_type' ] as String ;
59
+ return switch (WidgetType .fromRawString (rawWidgetType)) {
60
+ WidgetType .poll => PollWidgetData .fromJson (map),
61
+ WidgetType .unknown => UnsupportedWidgetData (json: map),
62
+ };
63
+ }
64
+
65
+ Object ? toJson ();
66
+ }
67
+
68
+ /// As in [WidgetData.widgetType] .
69
+ @JsonEnum (alwaysCreate: true )
70
+ enum WidgetType {
71
+ poll,
72
+ unknown;
73
+
74
+ static WidgetType fromRawString (String raw) => _byRawString[raw] ?? unknown;
75
+
76
+ static final _byRawString = _$WidgetTypeEnumMap
77
+ .map ((key, value) => MapEntry (value, key));
78
+ }
79
+
80
+ /// The data encoded in a submessage to make the message a poll widget.
81
+ @JsonSerializable (fieldRename: FieldRename .snake)
82
+ class PollWidgetData extends WidgetData {
83
+ @override
84
+ @JsonKey (includeToJson: true )
85
+ WidgetType get widgetType => WidgetType .poll;
86
+
87
+ /// The initial question and options on the poll.
88
+ final PollWidgetExtraData extraData;
89
+
90
+ PollWidgetData ({required this .extraData});
91
+
92
+ factory PollWidgetData .fromJson (Map <String , Object ?> json) =>
93
+ _$PollWidgetDataFromJson (json);
94
+
95
+ @override
96
+ Map <String , Object ?> toJson () => _$PollWidgetDataToJson (this );
97
+ }
98
+
99
+ /// As in [PollWidgetData.extraData] .
100
+ @JsonSerializable (fieldRename: FieldRename .snake)
101
+ class PollWidgetExtraData {
102
+ final String question;
103
+ final List <String > options;
104
+
105
+ const PollWidgetExtraData ({required this .question, required this .options});
106
+
107
+ factory PollWidgetExtraData .fromJson (Map <String , Object ?> json) =>
108
+ _$PollWidgetExtraDataFromJson (json);
109
+
110
+ Map <String , Object ?> toJson () => _$PollWidgetExtraDataToJson (this );
111
+ }
112
+
113
+ class UnsupportedWidgetData extends WidgetData {
114
+ @override
115
+ @JsonKey (includeToJson: true )
116
+ WidgetType get widgetType => WidgetType .unknown;
117
+
118
+ UnsupportedWidgetData ({required this .json});
119
+
120
+ final Object ? json;
121
+
122
+ @override
123
+ Object ? toJson () => json;
124
+ }
125
+
126
+ /// The data encoded in a submessage that acts on a poll.
127
+ sealed class PollEventSubmessage extends SubmessageData {
128
+ PollEventSubmessageType get type;
129
+
130
+ PollEventSubmessage ();
131
+
132
+ /// The key for identifying the [optionIndex] 'th option added by user
133
+ /// [senderId] to a poll.
134
+ ///
135
+ /// For options that are a part of the initial [PollWidgetData] , the
136
+ /// [senderId] should be `null` .
137
+ static String optionKey ({required int ? senderId, required int optionIndex}) =>
138
+ // "canned" is a canonical constant coined by the web client.
139
+ '${senderId ?? 'canned' },$optionIndex ' ;
140
+
141
+ factory PollEventSubmessage .fromJson (Map <String , Object ?> json) {
142
+ final rawPollEventType = json['type' ] as String ;
143
+ switch (PollEventSubmessageType .fromRawString (rawPollEventType)) {
144
+ case PollEventSubmessageType .newOption: return PollNewOptionEventSubmessage .fromJson (json);
145
+ case PollEventSubmessageType .question: return PollQuestionEventSubmessage .fromJson (json);
146
+ case PollEventSubmessageType .vote: return PollVoteEventSubmessage .fromJson (json);
147
+ case PollEventSubmessageType .unknown: return UnknownPollEventSubmessage (json: json);
148
+ }
149
+ }
150
+
151
+ Map <String , Object ?> toJson ();
152
+ }
153
+
154
+ /// A poll event when an option is added.
155
+ @JsonSerializable (fieldRename: FieldRename .snake)
156
+ class PollNewOptionEventSubmessage extends PollEventSubmessage {
157
+ @override
158
+ @JsonKey (includeToJson: true )
159
+ PollEventSubmessageType get type => PollEventSubmessageType .newOption;
160
+
161
+ final String option;
162
+ /// The index of last [option] added by the sender.
163
+ @JsonKey (name: 'idx' )
164
+ final int latestOptionIndex;
165
+
166
+ PollNewOptionEventSubmessage ({required this .option, required this .latestOptionIndex});
167
+
168
+ @override
169
+ factory PollNewOptionEventSubmessage .fromJson (Map <String , Object ?> json) =>
170
+ _$PollNewOptionEventSubmessageFromJson (json);
171
+
172
+ @override
173
+ Map <String , Object ?> toJson () => _$PollNewOptionEventSubmessageToJson (this );
174
+ }
175
+
176
+ /// A poll event when the question has been edited.
177
+ @JsonSerializable (fieldRename: FieldRename .snake)
178
+ class PollQuestionEventSubmessage extends PollEventSubmessage {
179
+ @override
180
+ @JsonKey (includeToJson: true )
181
+ PollEventSubmessageType get type => PollEventSubmessageType .question;
182
+
183
+ final String question;
184
+
185
+ PollQuestionEventSubmessage ({required this .question});
186
+
187
+ @override
188
+ factory PollQuestionEventSubmessage .fromJson (Map <String , Object ?> json) =>
189
+ _$PollQuestionEventSubmessageFromJson (json);
190
+
191
+ @override
192
+ Map <String , Object ?> toJson () => _$PollQuestionEventSubmessageToJson (this );
193
+ }
194
+
195
+ /// A poll event when a vote has been cast or removed.
196
+ @JsonSerializable (fieldRename: FieldRename .snake)
197
+ class PollVoteEventSubmessage extends PollEventSubmessage {
198
+ @override
199
+ @JsonKey (includeToJson: true )
200
+ PollEventSubmessageType get type => PollEventSubmessageType .vote;
201
+
202
+ /// The key of the affected option.
203
+ ///
204
+ /// See [PollEventSubmessage.optionKey] .
205
+ final String key;
206
+ @JsonKey (name: 'vote' , unknownEnumValue: PollVoteOp .unknown)
207
+ final PollVoteOp op;
208
+
209
+ PollVoteEventSubmessage ({required this .key, required this .op});
210
+
211
+ @override
212
+ factory PollVoteEventSubmessage .fromJson (Map <String , Object ?> json) {
213
+ final result = _$PollVoteEventSubmessageFromJson (json);
214
+ // Crunchy-shell validation
215
+ final segments = result.key.split (',' );
216
+ final [senderId, optionIndex] = segments;
217
+ if (senderId != 'canned' ) {
218
+ int .parse (senderId, radix: 10 );
219
+ }
220
+ int .parse (optionIndex, radix: 10 );
221
+ return result;
222
+ }
223
+
224
+ @override
225
+ Map <String , Object ?> toJson () => _$PollVoteEventSubmessageToJson (this );
226
+ }
227
+
228
+ /// As in [PollVoteEventSubmessage.op] .
229
+ @JsonEnum (valueField: 'apiValue' )
230
+ enum PollVoteOp {
231
+ add (apiValue: 1 ),
232
+ remove (apiValue: - 1 ),
233
+ unknown (apiValue: null );
234
+
235
+ const PollVoteOp ({required this .apiValue});
236
+
237
+ final int ? apiValue;
238
+
239
+ int ? toJson () => apiValue;
240
+ }
241
+
242
+ class UnknownPollEventSubmessage extends PollEventSubmessage {
243
+ @override
244
+ @JsonKey (includeToJson: true )
245
+ PollEventSubmessageType get type => PollEventSubmessageType .unknown;
246
+
247
+ final Map <String , Object ?> json;
248
+
249
+ UnknownPollEventSubmessage ({required this .json});
250
+
251
+ @override
252
+ Map <String , Object ?> toJson () => json;
253
+ }
254
+
255
+ /// As in [PollEventSubmessage.type] .
256
+ @JsonEnum (fieldRename: FieldRename .snake)
257
+ enum PollEventSubmessageType {
258
+ newOption,
259
+ question,
260
+ vote,
261
+ unknown;
262
+
263
+ static PollEventSubmessageType fromRawString (String raw) => _byRawString[raw]! ;
264
+
265
+ static final _byRawString = _$PollEventSubmessageTypeEnumMap
266
+ .map ((key, value) => MapEntry (value, key));
267
+ }
0 commit comments