Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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/core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import 'exception.dart';
///
/// When updating this, also update [kMinSupportedZulipFeatureLevel]
/// and the README.
// TODO(#992) address all TODO(server-6) and TODO(server-7)
// TODO(#1838) address all TODO(server-7)
const kMinSupportedZulipVersion = '7.0';

/// The Zulip feature level reserved for the [kMinSupportedZulipVersion] release.
Expand Down
10 changes: 7 additions & 3 deletions lib/api/model/events.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ sealed class Event {
case 'peer_remove': return SubscriptionPeerRemoveEvent.fromJson(json);
default: return UnexpectedEvent.fromJson(json);
}
// case 'muted_topics': … // TODO(#422) we ignore this feature on older servers
case 'user_status': return UserStatusEvent.fromJson(json);
case 'user_topic': return UserTopicEvent.fromJson(json);
case 'muted_users': return MutedUsersEvent.fromJson(json);
Expand Down Expand Up @@ -763,6 +762,7 @@ class SubscriptionUpdateEvent extends SubscriptionEvent {

final int streamId;

@JsonKey(unknownEnumValue: SubscriptionProperty.unknown)
final SubscriptionProperty property;

/// The new value, or null if we don't recognize the setting.
Expand All @@ -783,7 +783,6 @@ class SubscriptionUpdateEvent extends SubscriptionEvent {
assert(RegExp(r'^#[0-9a-f]{6}$').hasMatch(str));
return 0xff000000 | int.parse(str.substring(1), radix: 16);
case SubscriptionProperty.isMuted:
case SubscriptionProperty.inHomeView:
case SubscriptionProperty.pinToTop:
case SubscriptionProperty.desktopNotifications:
case SubscriptionProperty.audibleNotifications:
Expand Down Expand Up @@ -820,13 +819,18 @@ enum SubscriptionProperty {
color,

isMuted,
inHomeView,
pinToTop,
desktopNotifications,
audibleNotifications,
pushNotifications,
emailNotifications,
wildcardMentionsNotify,

/// A new, unrecognized property, or a deprecated one we don't use.
///
/// Could be `in_home_view`, deprecated in FL 139 (Server 6) but still sent
/// as of CZO on 2025-10-03.
// TODO(server-future) Remove `in_home_view` comment once it stops being sent.
unknown;

static SubscriptionProperty fromRawString(String raw) => _byRawString[raw] ?? unknown;
Expand Down
7 changes: 5 additions & 2 deletions lib/api/model/events.g.dart

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

8 changes: 3 additions & 5 deletions lib/api/model/initial_snapshot.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ class InitialSnapshot {
@JsonKey(defaultValue: 10000)
final int serverTypingStartedWaitPeriodMilliseconds;

// final List<…> mutedTopics; // TODO(#422) we ignore this feature on older servers

final List<MutedUserItem> mutedUsers;

// In the modern format because we pass `slim_presence`.
Expand Down Expand Up @@ -70,7 +68,7 @@ class InitialSnapshot {

final UserSettings userSettings;

final List<UserTopicItem>? userTopics; // TODO(server-6)
final List<UserTopicItem> userTopics;
Copy link
Member

Choose a reason for hiding this comment

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

This replaced muted_topics. Grepping (with git grep -i muted.?topic`), we have a commented-out reference to that, above, which we can delete.

Also a comment in the list of event types.


final GroupSettingValue? realmCanDeleteAnyMessageGroup; // TODO(server-10)

Expand Down Expand Up @@ -111,7 +109,7 @@ class InitialSnapshot {

final int maxFileUploadSizeMib;

final Uri? serverEmojiDataUrl; // TODO(server-6)
final Uri serverEmojiDataUrl;

final String? realmEmptyTopicDisplayName; // TODO(server-10)

Expand Down Expand Up @@ -284,7 +282,7 @@ class UserSettings {
)
TwentyFourHourTimeMode twentyFourHourTime;

bool? displayEmojiReactionUsers; // TODO(server-6)
bool displayEmojiReactionUsers;
Emojiset emojiset;
bool presenceEnabled;

Expand Down
12 changes: 5 additions & 7 deletions lib/api/model/initial_snapshot.g.dart

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

6 changes: 2 additions & 4 deletions lib/api/model/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class CustomProfileField {
final String name;
final String hint;
final String fieldData;
final bool? displayInProfileSummary; // TODO(server-6)
final bool? displayInProfileSummary;

CustomProfileField({
required this.id,
Expand Down Expand Up @@ -97,7 +97,7 @@ enum CustomProfileFieldType {
link(apiValue: 5),
user(apiValue: 6),
externalAccount(apiValue: 7),
pronouns(apiValue: 8), // TODO(server-6) newly added
pronouns(apiValue: 8),
unknown(apiValue: null);

const CustomProfileFieldType({
Expand Down Expand Up @@ -257,7 +257,6 @@ class StatusEmoji {
///
/// The absence of one of these means there is no change.
class UserStatusChange {
// final Option<bool> away; // deprecated in server-6 (FL-148); ignore
final Option<String?> text;
final Option<StatusEmoji?> emoji;

Expand Down Expand Up @@ -793,7 +792,6 @@ class Subscription extends ZulipStream {

bool pinToTop;
bool isMuted;
// final bool? inHomeView; // deprecated; ignore

/// As an int that dart:ui's Color constructor will take:
/// <https://api.flutter.dev/flutter/dart-ui/Color/Color.html>
Expand Down
4 changes: 1 addition & 3 deletions lib/model/channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ class ChannelStoreImpl extends HasUserStore with ChannelStore {
}

final topicVisibility = <int, TopicKeyedMap<UserTopicVisibilityPolicy>>{};
for (final item in initialSnapshot.userTopics ?? const <UserTopicItem>[]) {
for (final item in initialSnapshot.userTopics) {
if (_warnInvalidVisibilityPolicy(item.visibilityPolicy)) {
// Not a value we expect. Keep it out of our data structures. // TODO(log)
continue;
Expand Down Expand Up @@ -473,8 +473,6 @@ class ChannelStoreImpl extends HasUserStore with ChannelStore {
case SubscriptionProperty.isMuted:
// TODO(#1255) update [MessageListView] if affected
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:
Expand Down
12 changes: 0 additions & 12 deletions lib/model/message_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -873,12 +873,6 @@ class MessageListView with ChangeNotifier, _MessageSequence {
numBefore: kMessageListFetchBatchSize,
numAfter: 0,
processResult: (result) {
if (result.messages.isNotEmpty
&& result.messages.last.id == messages[0].id) {
// TODO(server-6): includeAnchor should make this impossible
result.messages.removeLast();
}

store.reconcileMessages(result.messages);
store.recentSenders.handleMessages(result.messages); // TODO(#824)

Expand Down Expand Up @@ -909,12 +903,6 @@ class MessageListView with ChangeNotifier, _MessageSequence {
numBefore: 0,
numAfter: kMessageListFetchBatchSize,
processResult: (result) {
if (result.messages.isNotEmpty
&& result.messages.first.id == messages.last.id) {
// TODO(server-6): includeAnchor should make this impossible
result.messages.removeAt(0);
}

store.reconcileMessages(result.messages);
store.recentSenders.handleMessages(result.messages); // TODO(#824)

Expand Down
7 changes: 1 addition & 6 deletions lib/model/store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1117,12 +1117,7 @@ class UpdateMachine {
final updateMachine = UpdateMachine.fromInitialSnapshot(
store: store, initialSnapshot: initialSnapshot);
updateMachine.poll();
if (initialSnapshot.serverEmojiDataUrl != null) {
// TODO(server-6): If the server is ancient, just skip trying to have
// a list of its emoji. (The old servers that don't provide
// serverEmojiDataUrl are already unsupported at time of writing.)
unawaited(updateMachine.fetchEmojiData(initialSnapshot.serverEmojiDataUrl!));
}
unawaited(updateMachine.fetchEmojiData(initialSnapshot.serverEmojiDataUrl));
store.presence.start();
return updateMachine;
}
Expand Down
20 changes: 4 additions & 16 deletions lib/model/unreads.dart
Original file line number Diff line number Diff line change
Expand Up @@ -453,12 +453,8 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
final newlyUnreadInDms = <DmNarrow, QueueList<int>>{};
for (final messageId in event.messages) {
final detail = event.messageDetails![messageId];
if (detail == null) { // TODO(log) if on Zulip 6.0+
// Happens as a bug in some cases before fixed in Zulip 6.0:
// https://chat.zulip.org/#narrow/stream/378-api-design/topic/unreads.20in.20unsubscribed.20streams/near/1458467
// TODO(server-6) remove Zulip 6.0 comment
continue;
}
if (detail == null) continue; // TODO(log)

if (detail.mentioned == true) {
mentions.add(messageId);
}
Expand Down Expand Up @@ -537,11 +533,7 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
final topics = streams[streamId] ??= makeTopicKeyedMap();
topics.update(topic,
ifAbsent: () => messageIds,
// setUnion dedupes existing and incoming unread IDs,
// so we tolerate zulip/zulip#22164, fixed in 6.0
// TODO(server-6) remove 6.0 comment
(existing) => setUnion(existing, messageIds),
);
(existing) => setUnion(existing, messageIds));
}

Comment on lines 534 to 538
Copy link
Member

Choose a reason for hiding this comment

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

This comment appears to have been explaining why we had this setUnion. Do we no longer need it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We discussed this in the office and decided to keep it, because it ensures the result is sorted when existing and messageIds are disjoint.

/// Remove [idsToRemove] from [streams] and [dms].
Expand Down Expand Up @@ -640,10 +632,6 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
void _addAllInDm(QueueList<int> messageIds, DmNarrow dmNarrow) {
dms.update(dmNarrow,
ifAbsent: () => messageIds,
// setUnion dedupes existing and incoming unread IDs,
// so we tolerate zulip/zulip#22164, fixed in 6.0
// TODO(server-6) remove 6.0 comment
(existing) => setUnion(existing, messageIds),
);
(existing) => setUnion(existing, messageIds));
}
}
9 changes: 2 additions & 7 deletions lib/widgets/action_sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1013,8 +1013,6 @@ void showMessageActionSheet({required BuildContext context, required Message mes
final isComposeBoxOffered = messageListPage.composeBoxState != null;

final isMessageRead = message.flags.contains(MessageFlag.read);
final markAsUnreadSupported = store.zulipFeatureLevel >= 155; // TODO(server-6)
final showMarkAsUnreadButton = markAsUnreadSupported && isMessageRead;

final isSenderMuted = store.isUserMuted(message.senderId);

Expand All @@ -1029,7 +1027,7 @@ void showMessageActionSheet({required BuildContext context, required Message mes
StarButton(message: message, pageContext: pageContext),
if (isComposeBoxOffered)
QuoteAndReplyButton(message: message, pageContext: pageContext),
if (showMarkAsUnreadButton)
if (isMessageRead)
MarkAsUnreadButton(message: message, pageContext: pageContext),
if (isSenderMuted)
// The message must have been revealed in order to open this action sheet.
Expand Down Expand Up @@ -1109,10 +1107,7 @@ bool _getShouldShowEditButton(BuildContext pageContext, Message message) {

final now = ZulipBinding.instance.utcNow().millisecondsSinceEpoch ~/ 1000;
final editLimit = store.realmMessageContentEditLimitSeconds;
final outsideEditLimit =
editLimit != null
&& editLimit != 0 // TODO(server-6) remove (pre-FL 138, 0 represents no limit)
&& now - message.timestamp > editLimit;
final outsideEditLimit = editLimit != null && now - message.timestamp > editLimit;

return message.senderId == store.selfUserId
&& isComposeBoxOffered
Expand Down
1 change: 0 additions & 1 deletion lib/widgets/actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ abstract final class ZulipAction {
Message message,
Narrow narrow,
) async {
assert(PerAccountStoreWidget.of(context).zulipFeatureLevel >= 155); // TODO(server-6)
final zulipLocalizations = ZulipLocalizations.of(context);
await updateMessageFlagsStartingFromAnchor(
context: context,
Expand Down
2 changes: 1 addition & 1 deletion lib/widgets/emoji_reaction.dart
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ class ReactionChipsList extends StatelessWidget {
Widget build(BuildContext context) {
final zulipLocalizations = ZulipLocalizations.of(context);
final store = PerAccountStoreWidget.of(context);
final displayEmojiReactionUsers = store.userSettings.displayEmojiReactionUsers ?? false;
final displayEmojiReactionUsers = store.userSettings.displayEmojiReactionUsers;
final showNames = displayEmojiReactionUsers && reactions.total <= 3;

Widget result = Wrap(spacing: 4, runSpacing: 4, crossAxisAlignment: WrapCrossAlignment.center,
Expand Down
8 changes: 3 additions & 5 deletions test/example_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -821,14 +821,13 @@ GetMessagesResult nearGetMessagesResult({
/// A GetMessagesResult the server might return when we request older messages.
GetMessagesResult olderGetMessagesResult({
required int anchor,
bool foundAnchor = false, // the value if the server understood includeAnchor false
required bool foundOldest,
bool historyLimited = false,
required List<Message> messages,
}) {
return GetMessagesResult(
anchor: anchor,
foundAnchor: foundAnchor,
foundAnchor: false,
foundNewest: false, // empirically always this, even when anchor happens to be latest
foundOldest: foundOldest,
historyLimited: historyLimited,
Expand All @@ -839,14 +838,13 @@ GetMessagesResult olderGetMessagesResult({
/// A GetMessagesResult the server might return when we request newer messages.
GetMessagesResult newerGetMessagesResult({
required int anchor,
bool foundAnchor = false, // the value if the server understood includeAnchor false
required bool foundNewest,
bool historyLimited = false,
required List<Message> messages,
}) {
return GetMessagesResult(
anchor: anchor,
foundAnchor: foundAnchor,
foundAnchor: false,
foundOldest: false,
foundNewest: foundNewest,
historyLimited: historyLimited,
Expand Down Expand Up @@ -1334,7 +1332,7 @@ InitialSnapshot initialSnapshot({
emojiset: Emojiset.google,
presenceEnabled: true,
),
userTopics: userTopics,
userTopics: userTopics ?? [],
// no default; allow `null` to simulate servers without this
realmCanDeleteAnyMessageGroup: realmCanDeleteAnyMessageGroup,
realmCanDeleteOwnMessageGroup: realmCanDeleteOwnMessageGroup,
Expand Down
14 changes: 0 additions & 14 deletions test/model/channel_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -121,20 +121,6 @@ void main() {
value: true));
check(store.subscriptions[stream.streamId]!.isMuted).isTrue();
});

test('SubscriptionProperty.inHomeView updates isMuted instead', () async {
final store = eg.store(initialSnapshot: eg.initialSnapshot(
streams: [stream],
subscriptions: [eg.subscription(stream, isMuted: false)],
));
check(store.subscriptions[stream.streamId]!.isMuted).isFalse();

await store.handleEvent(SubscriptionUpdateEvent(id: 1,
streamId: stream.streamId,
property: SubscriptionProperty.inHomeView,
value: false));
check(store.subscriptions[stream.streamId]!.isMuted).isTrue();
});
});

group('topic visibility', () {
Expand Down
Loading
Loading