Skip to content

Commit

Permalink
api: Add subscription events
Browse files Browse the repository at this point in the history
Add events for subscription with `op` values of `add`,
`remove`, and `update`. `peer_add` and `peer_remove`
left for zulip#374.
  • Loading branch information
sirpengi committed Nov 17, 2023
1 parent 21b5cd2 commit 0d149fe
Show file tree
Hide file tree
Showing 7 changed files with 447 additions and 0 deletions.
188 changes: 188 additions & 0 deletions lib/api/model/events.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ sealed class Event {
case 'update': return RealmUserUpdateEvent.fromJson(json);
default: return UnexpectedEvent.fromJson(json);
}
case 'subscription':
switch (json['op'] as String) {
case 'add': return SubscriptionAddEvent.fromJson(json);
case 'remove': return SubscriptionRemoveEvent.fromJson(json);
case 'update': return SubscriptionUpdateEvent.fromJson(json);
case 'peer_add': return SubscriptionPeerAddEvent.fromJson(json);
case 'peer_remove': return SubscriptionPeerRemoveEvent.fromJson(json);
default: return UnexpectedEvent.fromJson(json);
}
case 'stream':
switch (json['op'] as String) {
case 'create': return StreamCreateEvent.fromJson(json);
Expand Down Expand Up @@ -272,6 +281,185 @@ class RealmUserUpdateEvent extends RealmUserEvent {
Map<String, dynamic> toJson() => _$RealmUserUpdateEventToJson(this);
}

/// A Zulip event of type `subscription`.
///
/// The corresponding API docs are in several places for
/// different values of `op`; see subclasses.
sealed class SubscriptionEvent extends Event {
@override
@JsonKey(includeToJson: true)
String get type => 'subscription';

String get op;

SubscriptionEvent({required super.id});
}

/// A [SubscriptionEvent] with op `add`: https://zulip.com/api/get-events#subscription-add
@JsonSerializable(fieldRename: FieldRename.snake)
class SubscriptionAddEvent extends SubscriptionEvent {
@override
@JsonKey(includeToJson: true)
String get op => 'add';

final List<Subscription> subscriptions;

SubscriptionAddEvent({required super.id, required this.subscriptions});

factory SubscriptionAddEvent.fromJson(Map<String, dynamic> json) =>
_$SubscriptionAddEventFromJson(json);

@override
Map<String, dynamic> toJson() => _$SubscriptionAddEventToJson(this);
}

/// A [SubscriptionEvent] with op `remove`: https://zulip.com/api/get-events#subscription-remove
@JsonSerializable(fieldRename: FieldRename.snake)
class SubscriptionRemoveEvent extends SubscriptionEvent {
@override
@JsonKey(includeToJson: true)
String get op => 'remove';

@JsonKey(readValue: _readStreamIds)
final List<int> streamIds;

static List<int> _readStreamIds(Map json, String key) {
return (json['subscriptions'] as List<dynamic>)
.map((e) => (e as Map<String, dynamic>)['stream_id'] as int)
.toList();
}

SubscriptionRemoveEvent({required super.id, required this.streamIds});

factory SubscriptionRemoveEvent.fromJson(Map<String, dynamic> json) =>
_$SubscriptionRemoveEventFromJson(json);

@override
Map<String, dynamic> toJson() => _$SubscriptionRemoveEventToJson(this);
}

/// A [SubscriptionEvent] with op `update`: https://zulip.com/api/get-events#subscription-update
@JsonSerializable(fieldRename: FieldRename.snake)
class SubscriptionUpdateEvent extends SubscriptionEvent {
@override
@JsonKey(includeToJson: true)
String get op => 'update';

final int streamId;

final SubscriptionProperty property;

/// The new value, or null if we don't recognize the setting.
///
/// This will have the type appropriate for [property]; for example,
/// if the setting is boolean, then `value is bool` will always be true.
/// This invariant is enforced by [SubscriptionUpdateEvent.fromJson].
@JsonKey(readValue: _readValue)
final Object? value;

/// [value], with a check that its type corresponds to [property]
/// (e.g., `value as bool`).
static Object? _readValue(Map json, String key) {
final value = json['value'];
switch (SubscriptionProperty.fromRawString(json['property'] as String)) {
case SubscriptionProperty.color:
return value as String;
case SubscriptionProperty.isMuted:
case SubscriptionProperty.inHomeView:
case SubscriptionProperty.pinToTop:
case SubscriptionProperty.desktopNotifications:
case SubscriptionProperty.audibleNotifications:
case SubscriptionProperty.pushNotifications:
case SubscriptionProperty.emailNotifications:
case SubscriptionProperty.wildcardMentionsNotify:
return value as bool;
case SubscriptionProperty.unknown:
return null;
}
}

SubscriptionUpdateEvent({
required super.id,
required this.streamId,
required this.property,
required this.value,
});

factory SubscriptionUpdateEvent.fromJson(Map<String, dynamic> json) =>
_$SubscriptionUpdateEventFromJson(json);

@override
Map<String, dynamic> toJson() => _$SubscriptionUpdateEventToJson(this);
}

/// The name of a property in [Subscription].
///
/// Used in handling of [SubscriptionUpdateEvent].
@JsonEnum(fieldRename: FieldRename.snake, alwaysCreate: true)
enum SubscriptionProperty {
color,
isMuted,
inHomeView,
pinToTop,
desktopNotifications,
audibleNotifications,
pushNotifications,
emailNotifications,
wildcardMentionsNotify,
unknown;

static SubscriptionProperty fromRawString(String raw) => _byRawString[raw] ?? unknown;

static final _byRawString = _$SubscriptionPropertyEnumMap
.map((key, value) => MapEntry(value, key));
}

/// A [SubscriptionEvent] with op `peer_add`: https://zulip.com/api/get-events#subscription-peer_add
@JsonSerializable(fieldRename: FieldRename.snake)
class SubscriptionPeerAddEvent extends SubscriptionEvent {
@override
@JsonKey(includeToJson: true)
String get op => 'peer_add';

List<int> streamIds;
List<int> userIds;

SubscriptionPeerAddEvent({
required super.id,
required this.streamIds,
required this.userIds,
});

factory SubscriptionPeerAddEvent.fromJson(Map<String, dynamic> json) =>
_$SubscriptionPeerAddEventFromJson(json);

@override
Map<String, dynamic> toJson() => _$SubscriptionPeerAddEventToJson(this);
}

/// A [SubscriptionEvent] with op `peer_remove`: https://zulip.com/api/get-events#subscription-peer_remove
@JsonSerializable(fieldRename: FieldRename.snake)
class SubscriptionPeerRemoveEvent extends SubscriptionEvent {
@override
@JsonKey(includeToJson: true)
String get op => 'peer_remove';

List<int> streamIds;
List<int> userIds;

SubscriptionPeerRemoveEvent({
required super.id,
required this.streamIds,
required this.userIds,
});

factory SubscriptionPeerRemoveEvent.fromJson(Map<String, dynamic> json) =>
_$SubscriptionPeerRemoveEventFromJson(json);

@override
Map<String, dynamic> toJson() => _$SubscriptionPeerRemoveEventToJson(this);
}

/// A Zulip event of type `stream`.
///
/// The corresponding API docs are in several places for
Expand Down
110 changes: 110 additions & 0 deletions lib/api/model/events.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions lib/model/store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,52 @@ class PerAccountStore extends ChangeNotifier {
subscriptions.remove(stream.streamId);
}
notifyListeners();
} else if (event is SubscriptionAddEvent) {
assert(debugLog("server event: subscription/add"));
for (final subscription in event.subscriptions) {
subscriptions[subscription.streamId] = subscription;
}
notifyListeners();
} else if (event is SubscriptionRemoveEvent) {
assert(debugLog("server event: subscription/remove"));
for (final streamId in event.streamIds) {
subscriptions.remove(streamId);
}
notifyListeners();
} else if (event is SubscriptionUpdateEvent) {
assert(debugLog("server event: subscription/update"));
final subscription = subscriptions[event.streamId];
if (subscription == null) return; // TODO(log)
switch (event.property) {
case SubscriptionProperty.color:
subscription.color = event.value as String;
case SubscriptionProperty.isMuted:
subscription.isMuted = event.value as bool;
case SubscriptionProperty.inHomeView:
subscription.isMuted = !(event.value as bool);
case SubscriptionProperty.pinToTop:
subscription.pinToTop = event.value as bool;
case SubscriptionProperty.desktopNotifications:
subscription.desktopNotifications = event.value as bool;
case SubscriptionProperty.audibleNotifications:
subscription.audibleNotifications = event.value as bool;
case SubscriptionProperty.pushNotifications:
subscription.pushNotifications = event.value as bool;
case SubscriptionProperty.emailNotifications:
subscription.emailNotifications = event.value as bool;
case SubscriptionProperty.wildcardMentionsNotify:
subscription.wildcardMentionsNotify = event.value as bool;
case SubscriptionProperty.unknown:
// unrecognized property; do nothing
return;
}
notifyListeners();
} else if (event is SubscriptionPeerAddEvent) {
assert(debugLog("server event: subscription/peer_add"));
// TODO(#374): handle event
} else if (event is SubscriptionPeerRemoveEvent) {
assert(debugLog("server event: subscription/peer_remove"));
// TODO(#374): handle event
} else if (event is MessageEvent) {
assert(debugLog("server event: message ${jsonEncode(event.message.toJson())}"));
recentDmConversationsView.handleMessageEvent(event);
Expand Down
4 changes: 4 additions & 0 deletions test/api/model/events_checks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ extension AlertWordsEventChecks on Subject<AlertWordsEvent> {
Subject<List<String>> get alertWords => has((e) => e.alertWords, 'alertWords');
}

extension SubscriptionRemoveEventChecks on Subject<SubscriptionRemoveEvent> {
Subject<List<int>> get streamIds => has((e) => e.streamIds, 'streamIds');
}

extension MessageEventChecks on Subject<MessageEvent> {
Subject<Message> get message => has((e) => e.message, 'message');
}
Expand Down

0 comments on commit 0d149fe

Please sign in to comment.