Skip to content
1 change: 1 addition & 0 deletions lib/api/model/events.dart
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ class UserSettingsUpdateEvent extends Event {
switch (UserSettingName.fromRawString(json['property'] as String)) {
case UserSettingName.twentyFourHourTime:
return TwentyFourHourTimeMode.fromApiValue(value as bool?);
case UserSettingName.starredMessageCounts:
case UserSettingName.displayEmojiReactionUsers:
return value as bool;
case UserSettingName.emojiset:
Expand Down
1 change: 1 addition & 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.

5 changes: 5 additions & 0 deletions lib/api/model/initial_snapshot.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ class InitialSnapshot {

final UnreadMessagesSnapshot unreadMsgs;

final List<int> starredMessages;

final List<ZulipStream> streams;

// In register-queue, the name of this field is the singular "user_status",
Expand Down Expand Up @@ -175,6 +177,7 @@ class InitialSnapshot {
required this.subscriptions,
required this.channelFolders,
required this.unreadMsgs,
required this.starredMessages,
required this.streams,
required this.userStatuses,
required this.userSettings,
Expand Down Expand Up @@ -294,6 +297,7 @@ class UserSettings {
)
TwentyFourHourTimeMode twentyFourHourTime;

bool starredMessageCounts;
bool displayEmojiReactionUsers;
Emojiset emojiset;
bool presenceEnabled;
Expand All @@ -306,6 +310,7 @@ class UserSettings {

UserSettings({
required this.twentyFourHourTime,
required this.starredMessageCounts,
required this.displayEmojiReactionUsers,
required this.emojiset,
required this.presenceEnabled,
Expand Down
7 changes: 7 additions & 0 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.

1 change: 1 addition & 0 deletions lib/api/model/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ class UserStatusChange {
@JsonEnum(fieldRename: FieldRename.snake, alwaysCreate: true)
enum UserSettingName {
twentyFourHourTime,
starredMessageCounts,
displayEmojiReactionUsers,
emojiset,
presenceEnabled,
Expand Down
1 change: 1 addition & 0 deletions lib/api/model/model.g.dart

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

1 change: 1 addition & 0 deletions lib/api/route/settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Future<void> updateSettings(ApiConnection connection, {
// TODO(server-future) allow localeDefault for servers that support it
assert(mode != TwentyFourHourTimeMode.localeDefault);
value = mode.toJson();
case UserSettingName.starredMessageCounts:
case UserSettingName.displayEmojiReactionUsers:
value = valueRaw as bool;
case UserSettingName.emojiset:
Expand Down
39 changes: 32 additions & 7 deletions lib/model/message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ mixin MessageStore on ChannelStore {
/// All known messages, indexed by [Message.id].
Map<int, Message> get messages;

/// All starred messages, as message IDs.
Set<int> get starredMessages;
Comment on lines +27 to +28
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, I see.

I'm trying to think if there are other cases where this should get updated. I guess maybe not:

  • We can learn of messages through handleMessageEvent. But maybe those can't ever be starred.
  • We can learn of messages through reconcileMessages. But maybe if those are starred then they should already be in this set.

I think it'd be good to make that reasoning explicit, though, in those methods. Probably including asserts to verify those expectations.


/// [OutboxMessage]s sent by the user, indexed by [OutboxMessage.localMessageId].
Map<int, OutboxMessage> get outboxMessages;

Expand Down Expand Up @@ -208,6 +211,8 @@ mixin ProxyMessageStore on MessageStore {
@override
Map<int, Message> get messages => messageStore.messages;
@override
Set<int> get starredMessages => messageStore.starredMessages;
@override
Map<int, OutboxMessage> get outboxMessages => messageStore.outboxMessages;
@override
void registerMessageList(MessageListView view) =>
Expand Down Expand Up @@ -261,14 +266,19 @@ class _EditMessageRequestStatus {
}

class MessageStoreImpl extends HasChannelStore with MessageStore, _OutboxMessageStore {
MessageStoreImpl({required super.channels})
: // There are no messages in InitialSnapshot, so we don't have
// a use case for initializing MessageStore with nonempty [messages].
messages = {};
MessageStoreImpl({
required super.channels,
required List<int> initialStarredMessages,
}) :
messages = {},
starredMessages = Set.of(initialStarredMessages);

@override
final Map<int, Message> messages;

@override
final Set<int> starredMessages;

@override
final Set<MessageListView> _messageListViews = {};

Expand Down Expand Up @@ -717,23 +727,29 @@ class MessageStoreImpl extends HasChannelStore with MessageStore, _OutboxMessage
}
}

void handleDeleteMessageEvent(DeleteMessageEvent event) {
/// Handle a [DeleteMessageEvent]
/// and return whether the [PerAccountStore] should notify listeners.
bool handleDeleteMessageEvent(DeleteMessageEvent event) {
bool perAccountStoreShouldNotify = false;
for (final messageId in event.messageIds) {
messages.remove(messageId);
perAccountStoreShouldNotify |= starredMessages.remove(messageId);
_maybeStaleChannelMessages.remove(messageId);
_editMessageRequests.remove(messageId);
}
for (final view in _messageListViews) {
view.handleDeleteMessageEvent(event);
}
return perAccountStoreShouldNotify;
}

void handleUpdateMessageFlagsEvent(UpdateMessageFlagsEvent event) {
/// Handle an [UpdateMessageFlagsEvent]
/// and return whether the [PerAccountStore] should notify listeners.
bool handleUpdateMessageFlagsEvent(UpdateMessageFlagsEvent event) {
final isAdd = switch (event) {
UpdateMessageFlagsAddEvent() => true,
UpdateMessageFlagsRemoveEvent() => false,
};

if (isAdd && (event as UpdateMessageFlagsAddEvent).all) {
Comment on lines 752 to 753
Copy link
Member

Choose a reason for hiding this comment

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

nit: these stanzas don't become any more closely related with this change; in fact they become more separate, because this adds another stanza below which consumes isAdd separately

for (final message in messages.values) {
message.flags.add(event.flag);
Expand Down Expand Up @@ -766,6 +782,15 @@ class MessageStoreImpl extends HasChannelStore with MessageStore, _OutboxMessage
_notifyMessageListViews(event.messages);
}
}

if (event.flag == MessageFlag.starred) {
isAdd
? starredMessages.addAll(event.messages)
: starredMessages.removeAll(event.messages);
return true;
}

return false;
}

void handleReactionEvent(ReactionEvent event) {
Expand Down
13 changes: 10 additions & 3 deletions lib/model/store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,8 @@ class PerAccountStore extends PerAccountStoreBase with
presence: Presence(realm: realm,
initial: initialSnapshot.presences),
channels: channels,
messages: MessageStoreImpl(channels: channels),
messages: MessageStoreImpl(channels: channels,
initialStarredMessages: initialSnapshot.starredMessages),
unreads: Unreads(core: core, channelStore: channels,
initial: initialSnapshot.unreadMsgs),
recentDmConversationsView: RecentDmConversationsView(core: core,
Expand Down Expand Up @@ -776,6 +777,8 @@ class PerAccountStore extends PerAccountStoreBase with
switch (event.property!) {
case UserSettingName.twentyFourHourTime:
userSettings.twentyFourHourTime = event.value as TwentyFourHourTimeMode;
case UserSettingName.starredMessageCounts:
userSettings.starredMessageCounts = event.value as bool;
case UserSettingName.displayEmojiReactionUsers:
userSettings.displayEmojiReactionUsers = event.value as bool;
case UserSettingName.emojiset:
Expand Down Expand Up @@ -877,12 +880,16 @@ class PerAccountStore extends PerAccountStoreBase with
// specifically, their `senderId`s. By calling this after the
// aforementioned line, we'll lose reference to those messages.
recentSenders.handleDeleteMessageEvent(event, messages);
_messages.handleDeleteMessageEvent(event);
if (_messages.handleDeleteMessageEvent(event)) {
notifyListeners();
}
unreads.handleDeleteMessageEvent(event);
Comment on lines +883 to 886
Copy link
Member

Choose a reason for hiding this comment

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

It makes me a bit nervous to be calling notifyListeners at a point where the event has been only partially applied — the store is in a somewhat inconsistent state.

Instead let's delay the notifyListeners call to the end.


case UpdateMessageFlagsEvent():
assert(debugLog("server event: update_message_flags/${event.op} ${event.flag.toJson()}"));
_messages.handleUpdateMessageFlagsEvent(event);
if (_messages.handleUpdateMessageFlagsEvent(event)) {
notifyListeners();
}
unreads.handleUpdateMessageFlagsEvent(event);

case SubmessageEvent():
Expand Down
13 changes: 10 additions & 3 deletions lib/model/unreads.dart
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,7 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
// TODO(#370): maintain this count incrementally, rather than recomputing from scratch
int countInCombinedFeedNarrow() {
int c = 0;
for (final messageIds in dms.values) {
c = c + messageIds.length;
}
c += countInAllDms();
for (final MapEntry(key: streamId, value: topics) in streams.entries) {
for (final MapEntry(key: topic, value: messageIds) in topics.entries) {
if (channelStore.isTopicVisible(streamId, topic)) {
Expand Down Expand Up @@ -230,6 +228,15 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
// TODO: Implement unreads handling?
int countInKeywordSearchNarrow() => 0;

int countInAllDms() {
int c = 0;
for (final MapEntry(key: narrow, value: messageIds) in dms.entries) {
if (channelStore.shouldMuteDmConversation(narrow)) continue;
c += messageIds.length;
}
return c;
}

int countInNarrow(Narrow narrow) {
switch (narrow) {
case CombinedFeedNarrow():
Expand Down
Loading