Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement stream and topic muting #423

Merged
merged 18 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b6bcaaa
api [nfc]: Make remaining Event subtypes sealed
gnprice Nov 22, 2023
1180559
stream [nfc]: Move stream and subscription data to a StreamStore class
gnprice Nov 22, 2023
e08643e
stream: Assert stream IDs and names are distinct; fix a test to pass …
gnprice Nov 28, 2023
75caa5b
stream: Unify stream and subscription data
gnprice Nov 22, 2023
8084d4e
msglist: Leave out unread count on mark-read button
gnprice Dec 5, 2023
bae1491
api [nfc]: Order stream events before subscription events
gnprice Nov 22, 2023
ba5688a
api [nfc]: Move RealmEmojiItem to model, from initial_snapshot
gnprice Nov 22, 2023
9d433ca
api [nfc]: Move UserSettingName and Emojiset to model, from initial_s…
gnprice Nov 22, 2023
eb87d13
api: Add userTopics initial data and UserTopicEvent
gnprice Nov 22, 2023
91ae442
stream: Track user-topic data; add getters
gnprice Nov 22, 2023
77e2db8
inbox: Apply stream and topic muting to filter the inbox
gnprice Nov 22, 2023
5c778bd
store [nfc]: The event-poll loop has-a store, not is-a store
gnprice Nov 22, 2023
f8f88fe
store [nfc]: Make fromInitialSnapshot a factory constructor
gnprice Nov 22, 2023
ffa2d32
unread [nfc]: Give Unreads a reference to StreamStore
gnprice Nov 22, 2023
331d4ba
msglist test: Rearrange the substitute for a StreamUpdateEvent
gnprice Dec 19, 2023
0d03c8e
unread: Apply stream and topic muting to unread counts
gnprice Nov 22, 2023
221e76c
msglist: Apply stream and topic muting to MessageListView
gnprice Nov 22, 2023
48f8cd8
msglist: Optimize the _messageVisible check to avoid copying where po…
gnprice Nov 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions assets/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -342,12 +342,9 @@
"@serverUrlValidationErrorUnsupportedScheme": {
"description": "Error message when URL has an unsupported scheme."
},
"markAsReadLabel": "Mark {num, plural, =1{1 message} other{{num} messages}} as read",
"@markAsReadLabel": {
"description": "Button text to mark messages as read.",
"placeholders": {
"num": {"type": "int", "example": "4"}
}
"markAllAsReadLabel": "Mark all messages as read",
"@markAllAsReadLabel": {
"description": "Button text to mark messages as read."
},
"markAsReadComplete": "Marked {num, plural, =1{1 message} other{{num} messages}} as read.",
"@markAsReadComplete": {
Expand Down
130 changes: 79 additions & 51 deletions lib/api/model/events.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'package:json_annotation/json_annotation.dart';

import 'initial_snapshot.dart';
import 'model.dart';

part 'events.g.dart';
Expand Down Expand Up @@ -35,6 +34,13 @@ sealed class Event {
case 'update': return RealmUserUpdateEvent.fromJson(json);
default: return UnexpectedEvent.fromJson(json);
}
case 'stream':
switch (json['op'] as String) {
case 'create': return StreamCreateEvent.fromJson(json);
case 'delete': return StreamDeleteEvent.fromJson(json);
// TODO(#182): case 'update': …
default: return UnexpectedEvent.fromJson(json);
}
case 'subscription':
switch (json['op'] as String) {
case 'add': return SubscriptionAddEvent.fromJson(json);
Expand All @@ -44,13 +50,8 @@ sealed class Event {
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);
case 'delete': return StreamDeleteEvent.fromJson(json);
// TODO(#182): case 'update': …
default: return UnexpectedEvent.fromJson(json);
}
// case 'muted_topics': … // TODO(#422) we ignore this feature on older servers
case 'user_topic': return UserTopicEvent.fromJson(json);
case 'message': return MessageEvent.fromJson(json);
case 'update_message': return UpdateMessageEvent.fromJson(json);
case 'delete_message': return DeleteMessageEvent.fromJson(json);
Expand Down Expand Up @@ -195,7 +196,7 @@ class CustomProfileFieldsEvent extends Event {
///
/// The corresponding API docs are in several places for
/// different values of `op`; see subclasses.
abstract class RealmUserEvent extends Event {
sealed class RealmUserEvent extends Event {
@override
@JsonKey(includeToJson: true)
String get type => 'realm_user';
Expand Down Expand Up @@ -308,6 +309,57 @@ class RealmUserUpdateEvent extends RealmUserEvent {
Map<String, dynamic> toJson() => _$RealmUserUpdateEventToJson(this);
}

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

String get op;

StreamEvent({required super.id});
}

/// A [StreamEvent] with op `create`: https://zulip.com/api/get-events#stream-create
@JsonSerializable(fieldRename: FieldRename.snake)
class StreamCreateEvent extends StreamEvent {
@override
String get op => 'create';

final List<ZulipStream> streams;

StreamCreateEvent({required super.id, required this.streams});

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

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

/// A [StreamEvent] with op `delete`: https://zulip.com/api/get-events#stream-delete
@JsonSerializable(fieldRename: FieldRename.snake)
class StreamDeleteEvent extends StreamEvent {
@override
String get op => 'delete';

final List<ZulipStream> streams;

StreamDeleteEvent({required super.id, required this.streams});

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

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

// TODO(#182) StreamUpdateEvent, for a [StreamEvent] with op `update`:
// https://zulip.com/api/get-events#stream-update

/// A Zulip event of type `subscription`.
///
/// The corresponding API docs are in several places for
Expand Down Expand Up @@ -492,57 +544,33 @@ class SubscriptionPeerRemoveEvent extends SubscriptionEvent {
Map<String, dynamic> toJson() => _$SubscriptionPeerRemoveEventToJson(this);
}

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

String get op;

StreamEvent({required super.id});
}

/// A [StreamEvent] with op `create`: https://zulip.com/api/get-events#stream-create
/// A Zulip event of type `user_topic`: https://zulip.com/api/get-events#user_topic
@JsonSerializable(fieldRename: FieldRename.snake)
class StreamCreateEvent extends StreamEvent {
class UserTopicEvent extends Event {
@override
String get op => 'create';

final List<ZulipStream> streams;

StreamCreateEvent({required super.id, required this.streams});

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

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

/// A [StreamEvent] with op `delete`: https://zulip.com/api/get-events#stream-delete
@JsonSerializable(fieldRename: FieldRename.snake)
class StreamDeleteEvent extends StreamEvent {
@override
String get op => 'delete';
@JsonKey(includeToJson: true)
String get type => 'user_topic';

final List<ZulipStream> streams;
final int streamId;
final String topicName;
final int lastUpdated;
final UserTopicVisibilityPolicy visibilityPolicy;

StreamDeleteEvent({required super.id, required this.streams});
UserTopicEvent({
required super.id,
required this.streamId,
required this.topicName,
required this.lastUpdated,
required this.visibilityPolicy,
});

factory StreamDeleteEvent.fromJson(Map<String, dynamic> json) =>
_$StreamDeleteEventFromJson(json);
factory UserTopicEvent.fromJson(Map<String, dynamic> json) =>
_$UserTopicEventFromJson(json);

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

// TODO(#182) StreamUpdateEvent, for a [StreamEvent] with op `update`:
// https://zulip.com/api/get-events#stream-update

/// A Zulip event of type `message`: https://zulip.com/api/get-events#message
// TODO use [JsonSerializable] here too, using its customization features,
// in order to skip the boilerplate in [fromJson] and [toJson].
Expand Down
70 changes: 49 additions & 21 deletions lib/api/model/events.g.dart

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

Loading