@@ -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,231 @@ 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 .fromJson (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
+ final Object ? json;
119
+
120
+ UnsupportedWidgetData .fromJson (this .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 [idx] '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 idx}) =>
138
+ // "canned" is a canonical constant coined by the web client.
139
+ '${senderId ?? 'canned' },$idx ' ;
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 .fromJson (json);
148
+ }
149
+ }
150
+
151
+ Map <String , Object ?> toJson ();
152
+ }
153
+
154
+ /// As in [PollEventSubmessage.type] .
155
+ @JsonEnum (fieldRename: FieldRename .snake)
156
+ enum PollEventSubmessageType {
157
+ newOption,
158
+ question,
159
+ vote,
160
+ unknown;
161
+
162
+ static PollEventSubmessageType fromRawString (String raw) => _byRawString[raw]! ;
163
+
164
+ static final _byRawString = _$PollEventSubmessageTypeEnumMap
165
+ .map ((key, value) => MapEntry (value, key));
166
+ }
167
+
168
+ /// A poll event when an option is added.
169
+ @JsonSerializable (fieldRename: FieldRename .snake)
170
+ class PollNewOptionEventSubmessage extends PollEventSubmessage {
171
+ @override
172
+ @JsonKey (includeToJson: true )
173
+ PollEventSubmessageType get type => PollEventSubmessageType .newOption;
174
+
175
+ final String option;
176
+ /// A sequence number for this option, among options added to this poll
177
+ /// by this [Submessage.senderId] .
178
+ ///
179
+ /// See [PollEventSubmessage.optionKey] .
180
+ final int idx;
181
+
182
+ PollNewOptionEventSubmessage ({required this .option, required this .idx});
183
+
184
+ @override
185
+ factory PollNewOptionEventSubmessage .fromJson (Map <String , Object ?> json) =>
186
+ _$PollNewOptionEventSubmessageFromJson (json);
187
+
188
+ @override
189
+ Map <String , Object ?> toJson () => _$PollNewOptionEventSubmessageToJson (this );
190
+ }
191
+
192
+ /// A poll event when the question has been edited.
193
+ @JsonSerializable (fieldRename: FieldRename .snake)
194
+ class PollQuestionEventSubmessage extends PollEventSubmessage {
195
+ @override
196
+ @JsonKey (includeToJson: true )
197
+ PollEventSubmessageType get type => PollEventSubmessageType .question;
198
+
199
+ final String question;
200
+
201
+ PollQuestionEventSubmessage ({required this .question});
202
+
203
+ @override
204
+ factory PollQuestionEventSubmessage .fromJson (Map <String , Object ?> json) =>
205
+ _$PollQuestionEventSubmessageFromJson (json);
206
+
207
+ @override
208
+ Map <String , Object ?> toJson () => _$PollQuestionEventSubmessageToJson (this );
209
+ }
210
+
211
+ /// A poll event when a vote has been cast or removed.
212
+ @JsonSerializable (fieldRename: FieldRename .snake)
213
+ class PollVoteEventSubmessage extends PollEventSubmessage {
214
+ @override
215
+ @JsonKey (includeToJson: true )
216
+ PollEventSubmessageType get type => PollEventSubmessageType .vote;
217
+
218
+ /// The key of the affected option.
219
+ ///
220
+ /// See [PollEventSubmessage.optionKey] .
221
+ final String key;
222
+ @JsonKey (name: 'vote' , unknownEnumValue: PollVoteOp .unknown)
223
+ final PollVoteOp op;
224
+
225
+ PollVoteEventSubmessage ({required this .key, required this .op});
226
+
227
+ @override
228
+ factory PollVoteEventSubmessage .fromJson (Map <String , Object ?> json) {
229
+ final result = _$PollVoteEventSubmessageFromJson (json);
230
+ // Crunchy-shell validation
231
+ final segments = result.key.split (',' );
232
+ final [senderId, idx] = segments;
233
+ if (senderId != 'canned' ) {
234
+ int .parse (senderId, radix: 10 );
235
+ }
236
+ int .parse (idx, radix: 10 );
237
+ return result;
238
+ }
239
+
240
+ @override
241
+ Map <String , Object ?> toJson () => _$PollVoteEventSubmessageToJson (this );
242
+ }
243
+
244
+ /// As in [PollVoteEventSubmessage.op] .
245
+ @JsonEnum (valueField: 'apiValue' )
246
+ enum PollVoteOp {
247
+ add (apiValue: 1 ),
248
+ remove (apiValue: - 1 ),
249
+ unknown (apiValue: null );
250
+
251
+ const PollVoteOp ({required this .apiValue});
252
+
253
+ final int ? apiValue;
254
+
255
+ int ? toJson () => apiValue;
256
+ }
257
+
258
+ class UnknownPollEventSubmessage extends PollEventSubmessage {
259
+ @override
260
+ @JsonKey (includeToJson: true )
261
+ PollEventSubmessageType get type => PollEventSubmessageType .unknown;
262
+
263
+ final Map <String , Object ?> json;
264
+
265
+ UnknownPollEventSubmessage .fromJson (this .json);
266
+
267
+ @override
268
+ Map <String , Object ?> toJson () => json;
269
+ }
0 commit comments