Skip to content

Commit dadec5a

Browse files
committed
Merge remote-tracking branch 'pr/1629'
2 parents b0a7403 + 8d30ccd commit dadec5a

27 files changed

+889
-84
lines changed

lib/api/model/events.dart

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ sealed class Event {
6161
default: return UnexpectedEvent.fromJson(json);
6262
}
6363
// case 'muted_topics': … // TODO(#422) we ignore this feature on older servers
64+
case 'user_status': return UserStatusEvent.fromJson(json);
6465
case 'user_topic': return UserTopicEvent.fromJson(json);
6566
case 'muted_users': return MutedUsersEvent.fromJson(json);
6667
case 'message': return MessageEvent.fromJson(json);
@@ -708,6 +709,40 @@ class SubscriptionPeerRemoveEvent extends SubscriptionEvent {
708709
Map<String, dynamic> toJson() => _$SubscriptionPeerRemoveEventToJson(this);
709710
}
710711

712+
/// A Zulip event of type `user_status`: https://zulip.com/api/get-events#user_status
713+
@JsonSerializable(fieldRename: FieldRename.snake)
714+
class UserStatusEvent extends Event {
715+
@override
716+
@JsonKey(includeToJson: true)
717+
String get type => 'user_status';
718+
719+
final int userId;
720+
721+
@JsonKey(readValue: _readChange, includeToJson: false)
722+
final UserStatusChange change;
723+
724+
static Object? _readChange(Map<dynamic, dynamic> json, String key) {
725+
assert(json is Map<String, dynamic>); // value came through `fromJson` with this type
726+
json['status_text'] as String?;
727+
json['reaction_type'] as String?;
728+
json['emoji_code'] as String?;
729+
json['emoji_name'] as String?;
730+
return json;
731+
}
732+
733+
UserStatusEvent({
734+
required super.id,
735+
required this.userId,
736+
required this.change,
737+
});
738+
739+
factory UserStatusEvent.fromJson(Map<String, dynamic> json) =>
740+
_$UserStatusEventFromJson(json);
741+
742+
@override
743+
Map<String, dynamic> toJson() => _$UserStatusEventToJson(this);
744+
}
745+
711746
/// A Zulip event of type `user_topic`: https://zulip.com/api/get-events#user_topic
712747
@JsonSerializable(fieldRename: FieldRename.snake)
713748
class UserTopicEvent extends Event {

lib/api/model/events.g.dart

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

lib/api/model/initial_snapshot.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ class InitialSnapshot {
6666

6767
final List<ZulipStream> streams;
6868

69+
// In register-queue, the name of this field is the singular "user_status",
70+
// even though it actually contains user status information for all the users
71+
// that the self-user has access to. Therefore, we prefer to use the plural form.
72+
//
73+
// The API expresses each status as a change from the "zero status" (see
74+
// [UserStatus.zero]), with entries omitted for users whose status is the
75+
// zero status.
76+
@JsonKey(name: 'user_status')
77+
final Map<int, UserStatusChange> userStatuses;
78+
6979
// Servers pre-5.0 don't have `user_settings`, and instead provide whatever
7080
// user settings they support at toplevel in the initial snapshot. Since we're
7181
// likely to desupport pre-5.0 servers before wide release, we prefer to
@@ -154,6 +164,7 @@ class InitialSnapshot {
154164
required this.subscriptions,
155165
required this.unreadMsgs,
156166
required this.streams,
167+
required this.userStatuses,
157168
required this.userSettings,
158169
required this.userTopics,
159170
required this.realmWildcardMentionPolicy,

lib/api/model/initial_snapshot.g.dart

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

lib/api/model/model.dart

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:json_annotation/json_annotation.dart';
22

3+
import '../../basic.dart';
34
import '../../model/algorithms.dart';
45
import 'events.dart';
56
import 'initial_snapshot.dart';
@@ -158,6 +159,119 @@ class RealmEmojiItem {
158159
Map<String, dynamic> toJson() => _$RealmEmojiItemToJson(this);
159160
}
160161

162+
/// A user's status, with [text] and [emoji] parts.
163+
///
164+
/// If a part is null, that part is empty/unset.
165+
/// For a [UserStatus] with all parts empty, see [zero].
166+
class UserStatus {
167+
/// The text part (e.g. 'Working remotely'), or null if unset.
168+
///
169+
/// This won't be the empty string.
170+
final String? text;
171+
172+
/// The emoji part, or null if unset.
173+
final StatusEmoji? emoji;
174+
175+
const UserStatus({required this.text, required this.emoji}) : assert(text != '');
176+
177+
static const UserStatus zero = UserStatus(text: null, emoji: null);
178+
}
179+
180+
/// A user's status emoji, as in [UserStatus.emoji].
181+
class StatusEmoji {
182+
final String emojiName;
183+
final String emojiCode;
184+
final ReactionType reactionType;
185+
186+
const StatusEmoji({
187+
required this.emojiName,
188+
required this.emojiCode,
189+
required this.reactionType,
190+
}) : assert(emojiName != ''), assert(emojiCode != '');
191+
}
192+
193+
/// A change to part or all of a user's status.
194+
///
195+
/// The absence of one of these means there is no change.
196+
class UserStatusChange {
197+
// final Option<bool> away; // deprecated in server-6 (FL-148); ignore
198+
final Option<String?> text;
199+
final Option<StatusEmoji?> emoji;
200+
201+
const UserStatusChange({required this.text, required this.emoji});
202+
203+
factory UserStatusChange.fromJson(Map<String, dynamic> json) {
204+
return UserStatusChange(
205+
text: _textFromJson(json), emoji: _emojiFromJson(json));
206+
}
207+
208+
static Option<String?> _textFromJson(Map<String, dynamic> json) {
209+
return switch (json['status_text'] as String?) {
210+
null => OptionNone(),
211+
'' => OptionSome(null),
212+
final apiValue => OptionSome(apiValue),
213+
};
214+
}
215+
216+
static Option<StatusEmoji?> _emojiFromJson(Map<String, dynamic> json) {
217+
final reactionType = json['reaction_type'] as String?;
218+
final emojiCode = json['emoji_code'] as String?;
219+
final emojiName = json['emoji_name'] as String?;
220+
221+
if (reactionType == null || emojiCode == null || emojiName == null) {
222+
return OptionNone();
223+
} else if (reactionType == '' || emojiCode == '' || emojiName == '') {
224+
// Sometimes `reaction_type` is 'unicode_emoji' when the emoji is cleared.
225+
// This is an accident, to be handled by looking at `emoji_code` instead:
226+
// https://chat.zulip.org/#narrow/channel/378-api-design/topic/user.20status/near/2203132
227+
return OptionSome(null);
228+
} else {
229+
return OptionSome(StatusEmoji(
230+
reactionType: ReactionType.fromApiValue(reactionType),
231+
emojiCode: emojiCode,
232+
emojiName: emojiName));
233+
}
234+
}
235+
236+
Map<String, dynamic> toJson() {
237+
return {
238+
..._textToJson(text),
239+
..._emojiToJson(emoji),
240+
};
241+
}
242+
243+
Map<String, dynamic> _textToJson(Option<String?> text) {
244+
return {
245+
'status_text': switch (text) {
246+
OptionNone<String?>() => null,
247+
OptionSome<String?>(:var value) => value ?? '',
248+
}
249+
};
250+
}
251+
252+
Map<String, dynamic> _emojiToJson(Option<StatusEmoji?> emoji) {
253+
return switch (emoji) {
254+
OptionNone<StatusEmoji?>() => {
255+
'reaction_type': null,
256+
'emoji_code': null,
257+
'emoji_name': null,
258+
},
259+
OptionSome<StatusEmoji?>(:var value) =>
260+
value == null
261+
? {'reaction_type': '', 'emoji_code': '', 'emoji_name': ''}
262+
: {
263+
'reaction_type': value.reactionType,
264+
'emoji_code': value.emojiCode,
265+
'emoji_name': value.emojiName,
266+
},
267+
};
268+
}
269+
270+
UserStatus apply(UserStatus old) {
271+
return UserStatus(text: text.or(old.text), emoji: emoji.or(old.emoji));
272+
}
273+
}
274+
161275
/// The name of a user setting that has a property in [UserSettings].
162276
///
163277
/// In Zulip event-handling code (for [UserSettingsUpdateEvent]),

lib/api/model/reaction.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,4 +175,9 @@ enum ReactionType {
175175
zulipExtraEmoji;
176176

177177
String toJson() => _$ReactionTypeEnumMap[this]!;
178+
179+
static ReactionType fromApiValue(String value) => _byApiValue[value]!;
180+
181+
static final _byApiValue = _$ReactionTypeEnumMap
182+
.map((key, value) => MapEntry(value, key));
178183
}

0 commit comments

Comments
 (0)