Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion lib/api/model/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ sealed class Message {
Map<String, dynamic> toJson();
}

/// As in [Message.flags].
/// https://zulip.com/api/update-message-flags#available-flags
@JsonEnum(fieldRename: FieldRename.snake, alwaysCreate: true)
enum MessageFlag {
read,
Expand Down
16 changes: 16 additions & 0 deletions lib/api/model/narrow.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
typedef ApiNarrow = List<ApiNarrowElement>;

/// Resolve any [ApiNarrowDm] elements appropriately.
///
/// This encapsulates a server-feature check.
ApiNarrow resolveDmElements(ApiNarrow narrow, int zulipFeatureLevel) {
if (!narrow.any((element) => element is ApiNarrowDm)) {
return narrow;
}
final supportsOperatorDm = zulipFeatureLevel >= 177; // TODO(server-7)
return narrow.map((element) => switch (element) {
ApiNarrowDm() => element.resolve(legacy: !supportsOperatorDm),
_ => element,
}).toList();
}

/// An element in the list representing a narrow in the Zulip API.
///
/// Docs: <https://zulip.com/api/construct-narrow>
Expand Down Expand Up @@ -51,6 +65,8 @@ class ApiNarrowTopic extends ApiNarrowElement {
/// An instance directly of this class must not be serialized with [jsonEncode],
/// and more generally its [operator] getter must not be called.
/// Instead, call [resolve] and use the object it returns.
///
/// If part of [ApiNarrow] use [resolveDmElements].
class ApiNarrowDm extends ApiNarrowElement {
@override String get operator {
assert(false,
Expand Down
114 changes: 99 additions & 15 deletions lib/api/route/messages.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,21 +90,9 @@ Future<GetMessagesResult> getMessages(ApiConnection connection, {
bool? applyMarkdown,
// bool? useFirstUnreadAnchor // omitted because deprecated
}) {
if (narrow.any((element) => element is ApiNarrowDm)) {
final supportsOperatorDm = connection.zulipFeatureLevel! >= 177; // TODO(server-7)
narrow = narrow.map((element) => switch (element) {
Comment on lines -93 to -95
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The narrow.any conditional here is an optimization that avoids unnecessarily copying the list when there's nothing to change. Let's preserve that in the refactor.

ApiNarrowDm() => element.resolve(legacy: !supportsOperatorDm),
_ => element,
}).toList();
}
return connection.get('getMessages', GetMessagesResult.fromJson, 'messages', {
'narrow': narrow,
'anchor': switch (anchor) {
NumericAnchor(:var messageId) => messageId,
AnchorCode.newest => RawParameter('newest'),
AnchorCode.oldest => RawParameter('oldest'),
AnchorCode.firstUnread => RawParameter('first_unread'),
},
'narrow': resolveDmElements(narrow, connection.zulipFeatureLevel!),
'anchor': RawParameter(anchor.toJson()),
if (includeAnchor != null) 'include_anchor': includeAnchor,
'num_before': numBefore,
'num_after': numAfter,
Expand All @@ -119,17 +107,28 @@ Future<GetMessagesResult> getMessages(ApiConnection connection, {
sealed class Anchor {
/// This const constructor allows subclasses to have const constructors.
const Anchor();

String toJson();
}

/// An anchor value for [getMessages] other than a specific message ID.
///
/// https://zulip.com/api/get-messages#parameter-anchor
enum AnchorCode implements Anchor { newest, oldest, firstUnread }
@JsonEnum(fieldRename: FieldRename.snake, alwaysCreate: true)
enum AnchorCode implements Anchor {
newest, oldest, firstUnread;

@override
String toJson() => _$AnchorCodeEnumMap[this]!;
}

/// A specific message ID, used as an anchor in [getMessages].
class NumericAnchor extends Anchor {
const NumericAnchor(this.messageId);
final int messageId;

@override
String toJson() => messageId.toString();
}

@JsonSerializable(fieldRename: FieldRename.snake)
Expand Down Expand Up @@ -289,3 +288,88 @@ Future<void> removeReaction(ApiConnection connection, {
'reaction_type': RawParameter(reactionType.toJson()),
});
}

/// https://zulip.com/api/update-message-flags
Future<UpdateMessageFlagsResult> updateMessageFlags(ApiConnection connection, {
required List<int> messages,
required UpdateMessageFlagsOp op,
required MessageFlag flag,
}) {
return connection.post('updateMessageFlags', UpdateMessageFlagsResult.fromJson, 'messages/flags', {
'messages': messages,
'op': RawParameter(op.toJson()),
'flag': RawParameter(flag.toJson()),
});
}

/// An `op` value for [updateMessageFlags] and [updateMessageFlagsForNarrow].
@JsonEnum(fieldRename: FieldRename.snake, alwaysCreate: true)
enum UpdateMessageFlagsOp {
add,
remove;

String toJson() => _$UpdateMessageFlagsOpEnumMap[this]!;
}

@JsonSerializable(fieldRename: FieldRename.snake)
class UpdateMessageFlagsResult {
final List<int> messages;

UpdateMessageFlagsResult({
required this.messages,
});

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

Map<String, dynamic> toJson() => _$UpdateMessageFlagsResultToJson(this);
}

/// https://zulip.com/api/update-message-flags-for-narrow
///
/// This binding only supports feature levels 155+.
// TODO(server-6) remove FL 155+ mention in doc, and the related `assert`
Future<UpdateMessageFlagsForNarrowResult> updateMessageFlagsForNarrow(ApiConnection connection, {
required Anchor anchor,
bool? includeAnchor,
required int numBefore,
required int numAfter,
required ApiNarrow narrow,
required UpdateMessageFlagsOp op,
required MessageFlag flag,
}) {
assert(connection.zulipFeatureLevel! >= 155);
return connection.post('updateMessageFlagsForNarrow', UpdateMessageFlagsForNarrowResult.fromJson, 'messages/flags/narrow', {
'anchor': RawParameter(anchor.toJson()),
if (includeAnchor != null) 'include_anchor': includeAnchor,
'num_before': numBefore,
'num_after': numAfter,
'narrow': resolveDmElements(narrow, connection.zulipFeatureLevel!),
'op': RawParameter(op.toJson()),
'flag': RawParameter(flag.toJson()),
});
}

@JsonSerializable(fieldRename: FieldRename.snake)
class UpdateMessageFlagsForNarrowResult {
final int processedCount;
final int updatedCount;
final int? firstProcessedId;
final int? lastProcessedId;
final bool foundOldest;
final bool foundNewest;

UpdateMessageFlagsForNarrowResult({
required this.processedCount,
required this.updatedCount,
required this.firstProcessedId,
required this.lastProcessedId,
required this.foundOldest,
required this.foundNewest,
});

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

Map<String, dynamic> toJson() => _$UpdateMessageFlagsForNarrowResultToJson(this);
}
46 changes: 46 additions & 0 deletions lib/api/route/messages.g.dart

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

Loading