diff --git a/lib/api/model/initial_snapshot.dart b/lib/api/model/initial_snapshot.dart index bfd505c9af..86ecd4e9e0 100644 --- a/lib/api/model/initial_snapshot.dart +++ b/lib/api/model/initial_snapshot.dart @@ -7,6 +7,11 @@ part 'initial_snapshot.g.dart'; // https://zulip.com/api/register-queue#response @JsonSerializable(fieldRename: FieldRename.snake) class InitialSnapshot { + // Keep these fields in the order they appear in the API docs. + // (For many API types we choose a more logical order than the docs. + // But this one is so long that that'd make it become impossible to + // compare the lists by hand.) + final String? queueId; final int lastEventId; final int zulipFeatureLevel; @@ -21,6 +26,8 @@ class InitialSnapshot { final List subscriptions; + final List streams; + final int maxFileUploadSizeMib; @JsonKey(readValue: _readUsersIsActiveFallbackTrue) @@ -31,6 +38,7 @@ class InitialSnapshot { final List crossRealmBots; // TODO etc., etc. + // If adding fields, keep them all in the order they appear in the API docs. // `is_active` is sometimes absent: // https://chat.zulip.org/#narrow/stream/412-api-documentation/topic/.60is_active.60.20in.20.60.2Fregister.60.20response/near/1371603 @@ -59,6 +67,7 @@ class InitialSnapshot { required this.alertWords, required this.customProfileFields, required this.subscriptions, + required this.streams, required this.maxFileUploadSizeMib, required this.realmUsers, required this.realmNonActiveUsers, diff --git a/lib/api/model/initial_snapshot.g.dart b/lib/api/model/initial_snapshot.g.dart index 6db0420180..6c1f8049d8 100644 --- a/lib/api/model/initial_snapshot.g.dart +++ b/lib/api/model/initial_snapshot.g.dart @@ -24,6 +24,9 @@ InitialSnapshot _$InitialSnapshotFromJson(Map json) => subscriptions: (json['subscriptions'] as List) .map((e) => Subscription.fromJson(e as Map)) .toList(), + streams: (json['streams'] as List) + .map((e) => ZulipStream.fromJson(e as Map)) + .toList(), maxFileUploadSizeMib: json['max_file_upload_size_mib'] as int, realmUsers: (InitialSnapshot._readUsersIsActiveFallbackTrue(json, 'realm_users') @@ -50,6 +53,7 @@ Map _$InitialSnapshotToJson(InitialSnapshot instance) => 'alert_words': instance.alertWords, 'custom_profile_fields': instance.customProfileFields, 'subscriptions': instance.subscriptions, + 'streams': instance.streams, 'max_file_upload_size_mib': instance.maxFileUploadSizeMib, 'realm_users': instance.realmUsers, 'realm_non_active_users': instance.realmNonActiveUsers, diff --git a/lib/api/model/model.dart b/lib/api/model/model.dart index 00daeae420..a4320a28eb 100644 --- a/lib/api/model/model.dart +++ b/lib/api/model/model.dart @@ -4,7 +4,8 @@ part 'model.g.dart'; /// As in [InitialSnapshot.customProfileFields]. /// -/// https://zulip.com/api/register-queue#response +/// For docs, search for "custom_profile_fields:" +/// in . @JsonSerializable(fieldRename: FieldRename.snake) class CustomProfileField { final int id; @@ -50,7 +51,8 @@ class ProfileFieldUserData { /// cross_realm_bots are all extremely similar. They differ only in that /// cross_realm_bots has is_system_bot. /// -/// https://zulip.com/api/register-queue#response +/// For docs, search for "realm_users:" +/// in . @JsonSerializable(fieldRename: FieldRename.snake) class User { final int userId; @@ -118,18 +120,86 @@ class User { Map toJson() => _$UserToJson(this); } +/// As in `streams` in the initial snapshot. +/// +/// Not called `Stream` because dart:async uses that name. +/// +/// For docs, search for "if stream" +/// in . +@JsonSerializable(fieldRename: FieldRename.snake) +class ZulipStream { + final int streamId; + final String name; + final String description; + final String renderedDescription; + + final int dateCreated; + final int? firstMessageId; + + final bool inviteOnly; + final bool isWebPublic; // present since 2.1, according to /api/changelog + final bool historyPublicToSubscribers; + final int? messageRetentionDays; + + final int streamPostPolicy; // TODO enum + // final bool isAnnouncementOnly; // deprecated; ignore + + final int? canRemoveSubscribersGroupId; // TODO(server-6) + + ZulipStream({ + required this.streamId, + required this.name, + required this.description, + required this.renderedDescription, + required this.dateCreated, + required this.firstMessageId, + required this.inviteOnly, + required this.isWebPublic, + required this.historyPublicToSubscribers, + required this.messageRetentionDays, + required this.streamPostPolicy, + required this.canRemoveSubscribersGroupId, + }); + + factory ZulipStream.fromJson(Map json) => + _$ZulipStreamFromJson(json); + + Map toJson() => _$ZulipStreamToJson(this); +} + /// As in `subscriptions` in the initial snapshot. +/// +/// For docs, search for "subscriptions:" +/// in . @JsonSerializable(fieldRename: FieldRename.snake) class Subscription { + // First, fields that are about the stream and not the user's relation to it. + // These are largely the same as in [ZulipStream]. + final int streamId; final String name; final String description; final String renderedDescription; + final int dateCreated; - final bool inviteOnly; + final int? firstMessageId; + final int? streamWeeklyTraffic; + final bool inviteOnly; + final bool? isWebPublic; // TODO(server-??): doc doesn't say when added + final bool historyPublicToSubscribers; + final int? messageRetentionDays; // final List subscribers; // we register with includeSubscribers false + final int streamPostPolicy; // TODO enum + // final bool? isAnnouncementOnly; // deprecated; ignore + final String emailAddress; + + final int? canRemoveSubscribersGroupId; // TODO(server-6) + + // Then, fields that are specific to the subscription, + // i.e. the user's relationship to the stream. + final bool? desktopNotifications; final bool? emailNotifications; final bool? wildcardMentionsNotify; @@ -138,26 +208,11 @@ class Subscription { final bool pinToTop; - final String emailAddress; - final bool isMuted; - // final bool? inHomeView; // deprecated; ignore - // final bool? isAnnouncementOnly; // deprecated; ignore - final bool? isWebPublic; // TODO(server-??): doc doesn't say when added - final String color; - final int streamPostPolicy; // TODO enum - final int? messageRetentionDays; - final bool historyPublicToSubscribers; - - final int? firstMessageId; - final int? streamWeeklyTraffic; - - final int? canRemoveSubscribersGroupId; // TODO(server-6) - Subscription({ required this.streamId, required this.name, diff --git a/lib/api/model/model.g.dart b/lib/api/model/model.g.dart index 00d522edb3..7b7707383a 100644 --- a/lib/api/model/model.g.dart +++ b/lib/api/model/model.g.dart @@ -94,6 +94,38 @@ Map _$UserToJson(User instance) => { 'is_system_bot': instance.isSystemBot, }; +ZulipStream _$ZulipStreamFromJson(Map json) => ZulipStream( + streamId: json['stream_id'] as int, + name: json['name'] as String, + description: json['description'] as String, + renderedDescription: json['rendered_description'] as String, + dateCreated: json['date_created'] as int, + firstMessageId: json['first_message_id'] as int?, + inviteOnly: json['invite_only'] as bool, + isWebPublic: json['is_web_public'] as bool, + historyPublicToSubscribers: json['history_public_to_subscribers'] as bool, + messageRetentionDays: json['message_retention_days'] as int?, + streamPostPolicy: json['stream_post_policy'] as int, + canRemoveSubscribersGroupId: + json['can_remove_subscribers_group_id'] as int?, + ); + +Map _$ZulipStreamToJson(ZulipStream instance) => + { + 'stream_id': instance.streamId, + 'name': instance.name, + 'description': instance.description, + 'rendered_description': instance.renderedDescription, + 'date_created': instance.dateCreated, + 'first_message_id': instance.firstMessageId, + 'invite_only': instance.inviteOnly, + 'is_web_public': instance.isWebPublic, + 'history_public_to_subscribers': instance.historyPublicToSubscribers, + 'message_retention_days': instance.messageRetentionDays, + 'stream_post_policy': instance.streamPostPolicy, + 'can_remove_subscribers_group_id': instance.canRemoveSubscribersGroupId, + }; + Subscription _$SubscriptionFromJson(Map json) => Subscription( streamId: json['stream_id'] as int, name: json['name'] as String, diff --git a/lib/model/store.dart b/lib/model/store.dart index 6ea7ef178b..b975fd06fd 100644 --- a/lib/model/store.dart +++ b/lib/model/store.dart @@ -154,6 +154,8 @@ class PerAccountStore extends ChangeNotifier { .followedBy(initialSnapshot.realmNonActiveUsers) .followedBy(initialSnapshot.crossRealmBots) .map((user) => MapEntry(user.userId, user))), + streams = Map.fromEntries(initialSnapshot.streams.map( + (stream) => MapEntry(stream.streamId, stream))), subscriptions = Map.fromEntries(initialSnapshot.subscriptions.map( (subscription) => MapEntry(subscription.streamId, subscription))), maxFileUploadSizeMib = initialSnapshot.maxFileUploadSizeMib; @@ -161,8 +163,10 @@ class PerAccountStore extends ChangeNotifier { final Account account; final ApiConnection connection; + // TODO(#135): Keep all this data updated by handling Zulip events from the server. final String zulipVersion; final Map users; + final Map streams; final Map subscriptions; final int maxFileUploadSizeMib; // No event for this. diff --git a/test/example_data.dart b/test/example_data.dart index 8efa4809df..2fb88d9e04 100644 --- a/test/example_data.dart +++ b/test/example_data.dart @@ -109,6 +109,7 @@ final InitialSnapshot initialSnapshot = InitialSnapshot( alertWords: ['klaxon'], customProfileFields: [], subscriptions: [], // TODO add subscriptions to example initial snapshot + streams: [], // TODO add streams to example initial snapshot maxFileUploadSizeMib: 25, realmUsers: [], realmNonActiveUsers: [],